UNPKG

stripe

Version:
533 lines (532 loc) 18.7 kB
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 {};