Skip to content

Instantly share code, notes, and snippets.

@jessealama
Last active January 13, 2025 15:29
Show Gist options
  • Save jessealama/6497868587079a258575a2485f17fb21 to your computer and use it in GitHub Desktop.
Save jessealama/6497868587079a258575a2485f17fb21 to your computer and use it in GitHub Desktop.
A first stab at combining measure, decimal, and normalized decimal data types
export declare class Decimal128 {
constructor(value: string | number | bigint | NormalizedDecimal128);
// predicates
isFinite(): boolean;
isNegative(): boolean;
isNaN(): boolean;
isZero(): boolean;
// comparison
equals(other: Decimal128): boolean; // mathematical equality
lessThan(other: Decimal128): boolean;
// serialization
toJSON(): string;
toString(): string;
toFixed(opts?: {digits?: number}): string;
toLocaleString(locale?: string): string;
toPrecision(opts?: {digits?: number}): string;
// no arithmetic
// no rounding
// conversion
toNumber(): number;
toBigInt(): bigint;
toNormalizedDecimal(): NormalizedDecimal128; // or just "normalize"?
}
type ExpandedNumber = string // digit strings, not arbitrary strings
| number
| bigint
| Decimal128
| NormalizedDecimal128;
export declare class Measure {
constructor(value: ExpandedNumber, unit?: string, precision?: number);
convertTo(unit: string, precision?: number): Measure;
toString(): string;
toComponents(): {value: ExpandedNumber, unit: string}[];
getValue(): Decimal128;
}
export declare class NormalizedDecimal128 { // or just "Decimal"?
constructor(x: string | number | bigint | Decimal128);
// predicates
isFinite(): boolean;
isNegative(): boolean;
isNaN(): boolean;
isZero(): boolean;
// comparison
equals(other: NormalizedDecimal128): boolean;
lessThan(other: NormalizedDecimal128): boolean;
// arithmetic
abs(): NormalizedDecimal128;
add(x: NormalizedDecimal128): NormalizedDecimal128;
divide(x: NormalizedDecimal128): NormalizedDecimal128;
multiply(x: NormalizedDecimal128): NormalizedDecimal128;
negate(): NormalizedDecimal128;
subtract(x: NormalizedDecimal128): NormalizedDecimal128;
// serlialization
toJSON(): string;
toString(): string;
toFixed(opts?: {digits?: number}): string;
toLocaleString(locale?: string): string;
toPrecision(opts?: {digits?: number}): string;
// rounding
round(numFractionalDigits: number, mode: RoundingMode): NormalizedDecimal128;
// conversion
toDecimal128(): Decimal128;
toNumber(): number;
toBigInt(): bigint;
}
@jessealama
Copy link
Author

This gist sketches one possible approach in which the measure and decimal prposals are combined. Here we have three classes:

  • Decimal128, for "full" IEEE 754 Decimal128, but with no arithmetic
  • NormalizedDecimal128 for the subset of Decimal128 consisting only of normalized mathematical values
    • One could wonder whether we might subset even further here and disallow -0, NaN, and infinity.
  • Measure, offering up basics, but allowing the possibility of getting the underlying value as a (non-normalized) Decimal128.

The intention is to try to keep conversions explicit, with each class sticking to its own division of responsibility.

@jessealama
Copy link
Author

The dividing line between 262 and 402 here could be that Measure could exist in 262 but only supports the dimensionless unit (trying to use any other unit would throw). toLocaleString would exist in both decimal classes but would be supported in 402 only.

@jessealama
Copy link
Author

the absence of arithmetic support in the Decimal128 class reflects two discussions:

  • the main driver for support for full Decimal128 comes mainly from i18n use cases, where arithmetic needs seem to be largely absent. I've kept equals and lessThan in there, but perhaps those could also be dropped (or maybe lessThan, keeping equals).
  • the lack of a good theory for what the rules for quantum preservation should be for this class of values

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment