Files
2026-06-13 17:36:44 -07:00

533 lines
18 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
declare const __brand: unique symbol;
declare const __stripeType: unique symbol;
/**
* Rounding direction for Decimal operations.
*
* @remarks
* Seven modes corresponding to
* {@link https://standards.ieee.org/ieee/754/6210/ | IEEE 754-2019} §4.3
* rounding-direction attributes:
*
* | Direction | IEEE 754 name | Behavior | Examples (→ integer) |
* | -------------- | ----------------------- | --------------------------------- | ------------------------------------- |
* | `'ceil'` | `roundTowardPositive` | Toward +∞ | 1.1→2, -1.1→-1 |
* | `'floor'` | `roundTowardNegative` | Toward -∞ | 1.9→1, -1.1→-2 |
* | `'round-down'` | `roundTowardZero` | Toward zero (truncate) | 1.9→1, -1.9→-1 |
* | `'round-up'` | — | Away from zero | 1.1→2, -1.1→-2 |
* | `'half-up'` | `roundTiesToAway` | Nearest; ties away from zero | 0.5→1, -0.5→-1, 1.4→1 |
* | `'half-down'` | — | Nearest; ties toward zero | 0.5→0, -0.5→0, 1.6→2 |
* | `'half-even'` | `roundTiesToEven` | Nearest; ties to even (banker's) | 0.5→0, 1.5→2, 2.5→2, 3.5→4 |
*
* @public
*/
export type RoundDirection = 'ceil' | 'floor' | 'round-down' | 'round-up' | 'half-up' | 'half-down' | 'half-even';
/**
* Precision specification for {@link DecimalImpl.round}.
*
* @remarks
* Two modes are supported:
* - `"decimal-places"` — round to a fixed number of digits after the decimal point.
* - `"significant-figures"` — round to a fixed number of significant digits.
*
* @example
* ```ts
* // Round to 2 decimal places
* amount.round('half-even', { mode: 'decimal-places', value: 2 });
*
* // Round to 4 significant figures
* amount.round('half-up', { mode: 'significant-figures', value: 4 });
* ```
*
* @public
*/
export interface DecimalRoundingOptions {
mode: 'decimal-places' | 'significant-figures';
value: number;
}
/**
* Built-in rounding presets keyed by semantic name.
*
* @remarks
* This is an **open interface** — consumers can extend it via declaration
* merging to register custom presets that are accepted by
* {@link DecimalImpl.round}:
*
* ```ts
* declare module '@stripe/apps-extensibility-sdk/stdlib' {
* interface DecimalRoundingPresets {
* 'my-custom-preset': DecimalRoundingOptions;
* }
* }
* ```
*
* Built-in presets:
*
* | Preset | Equivalent DecimalRoundingOptions |
* | ------------------- | ------------------------------------------------------ |
* | `"ubb-usage-count"` | `{ mode: "significant-figures", value: 15 }` |
* | `"v1-api"` | `{ mode: "decimal-places", value: 12 }` |
*
* @public
*/
export interface DecimalRoundingPresets {
'ubb-usage-count': DecimalRoundingOptions;
'v1-api': DecimalRoundingOptions;
}
/**
* The IEEE 754 decimal128 coefficient size (34 digits) — the recommended
* precision for {@link DecimalImpl.div} when full precision is desired.
*
* @remarks
* Pass this as the `precision` argument to `div()` when you want the
* maximum available precision. Division requires explicit precision —
* no invisible defaults in financial code.
*
* @example
* ```ts
* // Use the full decimal128 precision explicitly
* a.div(b, DEFAULT_DIV_PRECISION, 'half-even');
* ```
*
* @public
*/
export declare const DEFAULT_DIV_PRECISION = 34;
/**
* Internal implementation of arbitrary-precision decimal arithmetic.
*
* @remarks
* Represents a decimal value as `coefficient × 10^exponent` using
* native `BigInt` for the coefficient, giving unlimited precision with
* no rounding on construction. Instances are always
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze | frozen}
* and all arithmetic produces new instances.
*
* This class is **not** exported directly — consumers interact with
* the branded {@link Decimal} type and the {@link Decimal | Decimal companion object}.
*
* @internal
*/
declare class DecimalImpl {
/** @internal */
private readonly _coefficient;
/** @internal */
private readonly _exponent;
/**
* Construct and normalise a decimal value.
*
* @param coefficient - The unscaled integer value.
* @param exponent - The power-of-ten scale factor.
*
* @internal
*/
constructor(coefficient: bigint, exponent: number);
/**
* Strip trailing zeros from `coefficient`, incrementing `exponent`
* for each zero removed. Zero always normalises to `(0n, 0)`.
*
* @param coefficient - Raw coefficient before normalisation.
* @param exponent - Raw exponent before normalisation.
* @returns A `[coefficient, exponent]` tuple with trailing zeros removed.
*
* @internal
*/
private static normalize;
/**
* Apply rounding to the result of an integer division.
*
* @remarks
* BigInt division truncates toward zero. This helper inspects the
* `remainder` to decide whether to adjust the truncated `quotient`
* by ±1 according to the chosen {@link RoundDirection}.
*
* The rounding direction is derived from the signs of `remainder`
* and `divisor`: when they agree the exact fractional part is
* positive (the truncation point is below the true value, so +1
* rounds to nearest); when they disagree the fractional part is
* negative (1 rounds to nearest).
*
* @param quotient - Truncated integer quotient (`dividend / divisor`).
* @param remainder - Division remainder (`dividend % divisor`).
* @param divisor - The divisor used in the division.
* @param direction - The rounding strategy to apply.
* @returns The rounded quotient.
*
* @internal
*/
private static roundDivision;
/**
* Return the sum of this value and `other`.
*
* @param other - The addend.
* @returns A new {@link Decimal} equal to `this + other`.
*
* @public
*/
add(other: Decimal): Decimal;
/**
* Return the difference of this value and `other`.
*
* @param other - The subtrahend.
* @returns A new {@link Decimal} equal to `this - other`.
*
* @public
*/
sub(other: Decimal): Decimal;
/**
* Return the product of this value and `other`.
*
* @param other - The multiplicand.
* @returns A new {@link Decimal} equal to `this × other`.
*
* @public
*/
mul(other: Decimal): Decimal;
/**
* Return the quotient of this value divided by `other`.
*
* @remarks
* Division scales the dividend to produce `precision` decimal digits
* in the result, then applies integer division and rounds the
* remainder according to `direction`.
*
* Division requires explicit rounding control — no invisible defaults
* in financial code. For full precision use {@link DEFAULT_DIV_PRECISION}
* (34, matching the IEEE 754 decimal128 coefficient size).
*
* @example
* ```ts
* Decimal.from('1').div(Decimal.from('3'), 5, 'half-up'); // "0.33333"
* Decimal.from('5').div(Decimal.from('2'), 0, 'half-up'); // "3"
* Decimal.from('5').div(Decimal.from('2'), 0, 'half-even'); // "2"
* ```
*
* @param other - The divisor. Must not be zero.
* @param precision - Maximum number of decimal digits in the result.
* @param direction - How to round when the exact quotient cannot
* be represented at the requested precision.
* @returns A new {@link Decimal} equal to `this ÷ other`, rounded to
* `precision` decimal places.
* @throws {@link Error} if `other` is zero.
* @throws {@link Error} if `precision` is negative or non-integer.
*
* @public
*/
div(other: Decimal, precision: number, direction: RoundDirection): Decimal;
/**
* Three-way comparison of this value with `other`.
*
* @example
* ```ts
* const a = Decimal.from('1.5');
* const b = Decimal.from('2');
* a.cmp(b); // -1
* b.cmp(a); // 1
* a.cmp(a); // 0
* ```
*
* @param other - The value to compare against.
* @returns `-1` if `this \< other`, `0` if equal, `1` if `this \> other`.
*
* @public
*/
cmp(other: Decimal): -1 | 0 | 1;
/**
* Return `true` if this value is numerically equal to `other`.
*
* @param other - The value to compare against.
* @returns `true` if `this === other` in value, `false` otherwise.
*
* @public
*/
eq(other: Decimal): boolean;
/**
* Return `true` if this value is strictly less than `other`.
*
* @param other - The value to compare against.
* @returns `true` if `this \< other`, `false` otherwise.
*
* @public
*/
lt(other: Decimal): boolean;
/**
* Return `true` if this value is less than or equal to `other`.
*
* @param other - The value to compare against.
* @returns `true` if `this ≤ other`, `false` otherwise.
*
* @public
*/
lte(other: Decimal): boolean;
/**
* Return `true` if this value is strictly greater than `other`.
*
* @param other - The value to compare against.
* @returns `true` if `this \> other`, `false` otherwise.
*
* @public
*/
gt(other: Decimal): boolean;
/**
* Return `true` if this value is greater than or equal to `other`.
*
* @param other - The value to compare against.
* @returns `true` if `this ≥ other`, `false` otherwise.
*
* @public
*/
gte(other: Decimal): boolean;
/**
* Return `true` if this value is exactly zero.
*
* @returns `true` if the value is zero, `false` otherwise.
*
* @public
*/
isZero(): boolean;
/**
* Return `true` if this value is strictly less than zero.
*
* @returns `true` if negative, `false` if zero or positive.
*
* @public
*/
isNegative(): boolean;
/**
* Return `true` if this value is strictly greater than zero.
*
* @returns `true` if positive, `false` if zero or negative.
*
* @public
*/
isPositive(): boolean;
/**
* Return the additive inverse of this value.
*
* @returns A new {@link Decimal} equal to `-this`.
*
* @public
*/
neg(): Decimal;
/**
* Return the absolute value.
*
* @returns A new {@link Decimal} equal to `|this|`. If this value is
* already non-negative, returns `this` (no allocation).
*
* @public
*/
abs(): Decimal;
/**
* Round this value to a specified precision.
*
* @remarks
* **Rounding directions** (IEEE 754-2019 §4.3):
*
* | Direction | Behavior |
* | -------------- | ---------------------------------------------- |
* | `'ceil'` | 1.1→2, -1.1→-1, 1.0→1 (toward +∞) |
* | `'floor'` | 1.9→1, -1.1→-2, 1.0→1 (toward -∞) |
* | `'round-down'` | 1.9→1, -1.9→-1 (toward zero / truncate) |
* | `'round-up'` | 1.1→2, -1.1→-2 (away from zero) |
* | `'half-up'` | 0.5→1, 1.5→2, -0.5→-1 (ties away from zero) |
* | `'half-down'` | 0.5→0, 1.5→1, -0.5→0 (ties toward zero) |
* | `'half-even'` | 0.5→0, 1.5→2, 2.5→2, 3.5→4 (ties to even) |
*
* **Precision** is specified as a {@link DecimalRoundingOptions} object
* or a preset name from {@link DecimalRoundingPresets}:
*
* @example
* ```ts
* // Using a preset
* amount.round('half-even', 'v1-api');
*
* // Using explicit options
* amount.round('half-even', { mode: 'decimal-places', value: 2 });
* amount.round('half-up', { mode: 'significant-figures', value: 4 });
* ```
*
* @param direction - How to round.
* @param options - A {@link DecimalRoundingOptions} object or key of {@link DecimalRoundingPresets}.
* @returns A new {@link Decimal} rounded to the specified precision.
* @throws {@link Error} if `options.value` is negative or non-integer.
* @throws {@link Error} if the preset name is not recognized.
*
* @public
*/
round(direction: RoundDirection, options: keyof DecimalRoundingPresets | DecimalRoundingOptions): Decimal;
/**
* Return a human-readable string representation.
*
* @remarks
* Plain notation for values whose digit count is at most 30, and
* scientific notation (`1.23E+40`) for larger values. Trailing zeros
* are never present because the internal representation is normalised.
*
* @public
*/
toString(): string;
/**
* Return the JSON-serialisable representation.
*
* @remarks
* Returns a plain string matching the Stripe API convention where
* decimal values are serialised as strings in JSON. Called
* automatically by `JSON.stringify`.
*
* @public
*/
toJSON(): string;
/**
* Convert to a JavaScript `number`.
*
* @remarks
* This is an explicit, intentionally lossy conversion. Use it only
* when you need a numeric value for display or interop with APIs
* that require `number`. Prefer {@link Decimal.toString | toString}
* or {@link Decimal.toFixed | toFixed} for lossless output.
*
* @public
*/
toNumber(): number;
/**
* Format this value as a fixed-point string with exactly
* `decimalPlaces` digits after the decimal point.
*
* @remarks
* Values are rounded according to `direction` when the internal
* precision exceeds the requested number of decimal places.
* The rounding direction is always required — no invisible defaults
* in financial code.
*
* @example
* ```ts
* Decimal.from('1.235').toFixed(2, 'half-up'); // "1.24"
* Decimal.from('1.225').toFixed(2, 'half-even'); // "1.22"
* Decimal.from('42').toFixed(3, 'half-up'); // "42.000"
* ```
*
* @param decimalPlaces - Number of digits after the decimal point.
* Must be a non-negative integer.
* @param direction - How to round when truncating excess digits.
* @returns A string with exactly `decimalPlaces` fractional digits.
* @throws {@link Error} if `decimalPlaces` is negative or non-integer.
*
* @public
*/
toFixed(decimalPlaces: number, direction: RoundDirection): string;
/**
* Return a string primitive when the runtime coerces the value.
*
* @remarks
* Deliberately returns a `string` (not a `number`) to discourage
* silent precision loss through implicit arithmetic coercion.
* When used in a numeric context (for example, `+myDecimal`), the
* JavaScript runtime will first call this method and then coerce
* the resulting string to a `number`, which may lose precision.
* Callers should prefer the explicit
* {@link Decimal.toNumber | toNumber} method when an IEEE 754
* `number` is required.
*
* @public
*/
valueOf(): string;
}
/**
* Arbitrary-precision decimal type for billing calculations.
*
* @remarks
* `Decimal` is a branded wrapper around an internal class that stores
* values as `coefficient × 10^exponent` using `BigInt`. It avoids
* every common binary floating-point pitfall — `Decimal.from('0.1').add(Decimal.from('0.2'))`
* is exactly `0.3`.
*
* Instances are immutable (frozen) and all arithmetic returns a new
* `Decimal`. The type carries two brand symbols so the type system
* prevents accidental assignment from plain `number`, `string`, or
* `bigint`.
*
* Create values via the companion object:
*
* @example
* ```ts
* import { Decimal, RoundDirection } from '@stripe/apps-extensibility-sdk/stdlib';
*
* const price = Decimal.from('19.99');
* const tax = price.mul(Decimal.from('0.0825'));
* const total = price.add(tax);
*
* console.log(total.toFixed(2, 'half-up')); // "21.64"
* console.log(JSON.stringify({ total })); // '{"total":"21.639175"}'
* console.log(total.toFixed(2, 'half-even')); // "21.64"
* ```
*
* @public
*/
export type Decimal = DecimalImpl & {
readonly [__brand]: 'Decimal';
readonly [__stripeType]: 'decimal';
};
/**
* Check whether a value is a {@link Decimal} instance.
*
* @remarks
* Use this instead of `instanceof` — the underlying class is not
* publicly exported, so `instanceof` checks are not available to
* consumers.
*
* @example
* ```ts
* if (isDecimal(value)) {
* value.add(Decimal.from('1')); // value is Decimal
* }
* ```
*
* @public
*/
export declare function isDecimal(value: unknown): value is Decimal;
/**
* Companion object for creating {@link Decimal} instances.
*
* @public
*/
export declare const Decimal: {
/**
* Create a {@link Decimal} from a string, number, or bigint.
*
* @remarks
* - **string**: Parsed as a decimal literal. Accepts an optional sign,
* integer digits, an optional fractional part, and an optional `e`/`E`
* exponent. Leading/trailing whitespace is trimmed.
* - **number**: Must be finite. Converted via `Number.prototype.toString()`
* then parsed, so `Decimal.from(0.1)` produces `"0.1"` (not the
* 53-bit binary approximation).
* - **bigint**: Treated as an integer with exponent 0.
*
* @example
* ```ts
* Decimal.from('1.23'); // string
* Decimal.from(42); // number
* Decimal.from(100n); // bigint
* Decimal.from('1.5e3'); // scientific notation → 1500
* ```
*
* @param value - The value to convert.
* @returns A new frozen {@link Decimal} instance.
* @throws {@link Error} if `value` is a non-finite number, an empty
* string, or a string that does not match the decimal literal grammar.
*
* @public
*/
from(value: string | number | bigint): Decimal;
/**
* The {@link Decimal} value representing zero.
*
* @remarks
* Pre-allocated singleton — prefer `Decimal.zero` over
* `Decimal.from(0)` to avoid an unnecessary allocation.
*
* @public
*/
zero: Decimal;
};
export {};