UNPKG

stripe

Version:
851 lines 32.9 kB
/** * Maps built-in preset names to their {@link DecimalRoundingOptions}. * Used internally by {@link DecimalImpl.round}. * * @internal */ const ROUNDING_PRESETS = { 'ubb-usage-count': { mode: 'significant-figures', value: 15 }, 'v1-api': { mode: 'decimal-places', value: 12 }, }; /** * 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 const DEFAULT_DIV_PRECISION = 34; /** * Maximum number of digits in plain (non-exponential) notation produced * by {@link DecimalImpl.toString}. Values exceeding this threshold are * emitted in scientific notation (`1.23E+40`). * * @internal */ const PLAIN_NOTATION_DIGIT_LIMIT = 30; /** * Maximum absolute value for the internal exponent. * * @remarks * This bound also implicitly limits exponent differences used in * arithmetic (e.g., scaling by `10^exponentDiff`), preventing * astronomically large BigInt allocations that could hang or * exhaust the process. * * The chosen limit is intentionally conservative but still far beyond * any magnitude needed for typical financial or billing calculations. * * @internal */ const MAX_EXPONENT = 1000000; /** * 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 */ class DecimalImpl { /** * Construct and normalise a decimal value. * * @param coefficient - The unscaled integer value. * @param exponent - The power-of-ten scale factor. * * @internal */ constructor(coefficient, exponent) { const [normalizedCoef, normalizedExp] = DecimalImpl.normalize(coefficient, exponent); this._coefficient = normalizedCoef; this._exponent = normalizedExp; Object.freeze(this); } /** * 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 */ static normalize(coefficient, exponent) { if (coefficient === 0n) { return [0n, 0]; } let coef = coefficient; let exp = exponent; while (coef !== 0n && coef % 10n === 0n) { coef /= 10n; exp += 1; } return [coef, exp]; } /** * 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 */ static roundDivision(quotient, remainder, divisor, direction) { if (remainder === 0n) { return quotient; } // 'round-down': truncate toward zero — BigInt division already does this. if (direction === 'round-down') { return quotient; } // The sign of remainder/divisor tells us which side of the truncation // point the exact value lies on. // Same sign → fractional part is positive (exact value > quotient) → +1 adjusts upward. // Opposite sign → fractional part is negative (exact value < quotient) → -1 adjusts downward. const roundDir = remainder > 0n === divisor > 0n ? 1n : -1n; // 'round-up': away from zero whenever there is any remainder. if (direction === 'round-up') { return quotient + roundDir; } // 'ceil': toward positive infinity. // If the fractional part is positive (roundDir === 1n), round up. // If the fractional part is negative (roundDir === -1n), truncation already went toward +∞. if (direction === 'ceil') { return roundDir === 1n ? quotient + 1n : quotient; } // 'floor': toward negative infinity. // If the fractional part is negative (roundDir === -1n), round down. // If the fractional part is positive (roundDir === 1n), truncation already went toward -∞. if (direction === 'floor') { return roundDir === -1n ? quotient - 1n : quotient; } // For the half-* modes we need to compare the remainder to exactly half the divisor. const absRemainder = remainder < 0n ? -remainder : remainder; const absDivisor = divisor < 0n ? -divisor : divisor; const doubled = absRemainder * 2n; let cmp; if (doubled === absDivisor) { cmp = 0; } else if (doubled < absDivisor) { cmp = -1; } else { cmp = 1; } if (cmp < 0) { // Less than half — truncation is already the nearest value. return quotient; } if (cmp > 0) { // More than half — round to nearest (away from truncation point). return quotient + roundDir; } // Exactly half — tie-breaking depends on the chosen mode. if (direction === 'half-up') { // Round away from zero. return quotient + roundDir; } if (direction === 'half-down') { // Round toward zero — stay at the truncated quotient. return quotient; } // HALF_EVEN: round to nearest even. if (quotient % 2n === 0n) { // Already even — stay at truncation. return quotient; } else { // Odd — adjust to make even. return quotient + roundDir; } } // ------------------------------------------------------------------- // Arithmetic // ------------------------------------------------------------------- /** * Return the sum of this value and `other`. * * @param other - The addend. * @returns A new {@link Decimal} equal to `this + other`. * * @public */ add(other) { const otherImpl = other; // Align exponents — use the smaller (more precision) exponent as target. if (this._exponent === otherImpl._exponent) { return new DecimalImpl(this._coefficient + otherImpl._coefficient, this._exponent); } if (this._exponent < otherImpl._exponent) { const scale = 10n ** BigInt(otherImpl._exponent - this._exponent); return new DecimalImpl(this._coefficient + otherImpl._coefficient * scale, this._exponent); } else { const scale = 10n ** BigInt(this._exponent - otherImpl._exponent); return new DecimalImpl(this._coefficient * scale + otherImpl._coefficient, otherImpl._exponent); } } /** * Return the difference of this value and `other`. * * @param other - The subtrahend. * @returns A new {@link Decimal} equal to `this - other`. * * @public */ sub(other) { const otherImpl = other; if (this._exponent === otherImpl._exponent) { return new DecimalImpl(this._coefficient - otherImpl._coefficient, this._exponent); } if (this._exponent < otherImpl._exponent) { const scale = 10n ** BigInt(otherImpl._exponent - this._exponent); return new DecimalImpl(this._coefficient - otherImpl._coefficient * scale, this._exponent); } else { const scale = 10n ** BigInt(this._exponent - otherImpl._exponent); return new DecimalImpl(this._coefficient * scale - otherImpl._coefficient, otherImpl._exponent); } } /** * Return the product of this value and `other`. * * @param other - The multiplicand. * @returns A new {@link Decimal} equal to `this × other`. * * @public */ mul(other) { const otherImpl = other; return new DecimalImpl(this._coefficient * otherImpl._coefficient, this._exponent + otherImpl._exponent); } /** * 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, precision, direction) { if (precision < 0 || !Number.isInteger(precision)) { throw new Error('precision must be a non-negative integer'); } const otherImpl = other; if (otherImpl._coefficient === 0n) { throw new Error('Division by zero'); } // result_coefficient = this.coefficient × 10^(thisExp - otherExp + precision) / other.coefficient // result_exponent = -precision const scale = this._exponent - otherImpl._exponent + precision; let quotient; let remainder; let roundingDivisor; if (scale >= 0) { const scaledDividend = this._coefficient * 10n ** BigInt(scale); quotient = scaledDividend / otherImpl._coefficient; remainder = scaledDividend % otherImpl._coefficient; roundingDivisor = otherImpl._coefficient; } else { // Negative scale: shift the power onto the divisor side to avoid // BigInt exponentiation with a negative exponent (which throws). const scaledDivisor = otherImpl._coefficient * 10n ** BigInt(-scale); quotient = this._coefficient / scaledDivisor; remainder = this._coefficient % scaledDivisor; roundingDivisor = scaledDivisor; } const roundedQuotient = DecimalImpl.roundDivision(quotient, remainder, roundingDivisor, direction); return new DecimalImpl(roundedQuotient, -precision); } // ------------------------------------------------------------------- // Comparison // ------------------------------------------------------------------- /** * 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) { const otherImpl = other; if (this._exponent === otherImpl._exponent) { if (this._coefficient < otherImpl._coefficient) return -1; if (this._coefficient > otherImpl._coefficient) return 1; return 0; } if (this._exponent < otherImpl._exponent) { // this has smaller exponent — scale other's coefficient to match. const scale = 10n ** BigInt(otherImpl._exponent - this._exponent); const scaledOther = otherImpl._coefficient * scale; if (this._coefficient < scaledOther) return -1; if (this._coefficient > scaledOther) return 1; return 0; } else { // other has smaller exponent — scale this's coefficient to match. const scale = 10n ** BigInt(this._exponent - otherImpl._exponent); const scaledThis = this._coefficient * scale; if (scaledThis < otherImpl._coefficient) return -1; if (scaledThis > otherImpl._coefficient) return 1; return 0; } } /** * 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) { return this.cmp(other) === 0; } /** * 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) { return this.cmp(other) === -1; } /** * 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) { return this.cmp(other) <= 0; } /** * 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) { return this.cmp(other) === 1; } /** * 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) { return this.cmp(other) >= 0; } // ------------------------------------------------------------------- // Predicates // ------------------------------------------------------------------- /** * Return `true` if this value is exactly zero. * * @returns `true` if the value is zero, `false` otherwise. * * @public */ isZero() { return this._coefficient === 0n; } /** * Return `true` if this value is strictly less than zero. * * @returns `true` if negative, `false` if zero or positive. * * @public */ isNegative() { return this._coefficient < 0n; } /** * Return `true` if this value is strictly greater than zero. * * @returns `true` if positive, `false` if zero or negative. * * @public */ isPositive() { return this._coefficient > 0n; } // ------------------------------------------------------------------- // Unary operations // ------------------------------------------------------------------- /** * Return the additive inverse of this value. * * @returns A new {@link Decimal} equal to `-this`. * * @public */ neg() { return new DecimalImpl(-this._coefficient, this._exponent); } /** * 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() { if (this._coefficient < 0n) { return new DecimalImpl(-this._coefficient, this._exponent); } return this; } // ------------------------------------------------------------------- // Rounding // ------------------------------------------------------------------- /** * 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, options) { const resolved = typeof options === 'string' ? // Declaration merging allows consumers to add keys at compile time, but // ROUNDING_PRESETS only knows about built-in keys at runtime. The double // cast through `unknown` is intentional: we want an undefined-safe lookup // so the runtime guard below can produce a clear error for unrecognised // (e.g. declaration-merged) preset names that were not also added to // ROUNDING_PRESETS. ROUNDING_PRESETS[options] : options; if (resolved === undefined) { throw new Error(`Unknown rounding preset: "${options}"`); } if (resolved.value < 0 || !Number.isInteger(resolved.value)) { throw new Error('DecimalRoundingOptions.value must be a non-negative integer'); } if (resolved.mode === 'decimal-places') { // Reuse toFixed logic: round to resolved.value decimal places then re-parse. const fixed = this.toFixed(resolved.value, direction); return Decimal.from(fixed); } // significant-figures: round to resolved.value total significant digits. if (this._coefficient === 0n) { return this; } const coeffStr = this._coefficient < 0n ? (-this._coefficient).toString() : this._coefficient.toString(); const currentSigFigs = coeffStr.length; if (resolved.value === 0) { // 0 significant figures is a degenerate case — return zero. return Decimal.zero; } if (currentSigFigs <= resolved.value) { // Already at or below requested precision — no rounding needed. return this; } // We need to reduce the number of significant figures. // The number of digits to drop from the coefficient: const digitsToTrim = currentSigFigs - resolved.value; const divisor = 10n ** BigInt(digitsToTrim); const quotient = this._coefficient / divisor; const remainder = this._coefficient % divisor; const rounded = DecimalImpl.roundDivision(quotient, remainder, divisor, direction); // The new exponent shifts to account for trimmed digits. return new DecimalImpl(rounded, this._exponent + digitsToTrim); } // ------------------------------------------------------------------- // Conversion / serialisation // ------------------------------------------------------------------- /** * 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() { if (this._coefficient === 0n) { return '0'; } const coeffStr = this._coefficient.toString(); const isNeg = coeffStr.startsWith('-'); const absCoeffStr = isNeg ? coeffStr.slice(1) : coeffStr; if (this._exponent < 0) { const decimalPlaces = -this._exponent; // Guard against unbounded string allocation for extreme negative // exponents (e.g. 1e-1000000 would otherwise produce a million-char // string of leading zeros). Switch to scientific notation when the // number of leading zeros alone exceeds the digit limit. Normal // fractional values (e.g. 34-digit division results) pass through. const leadingZeroCount = decimalPlaces >= absCoeffStr.length ? decimalPlaces - absCoeffStr.length : 0; if (leadingZeroCount > PLAIN_NOTATION_DIGIT_LIMIT) { if (absCoeffStr.length === 1) { return `${coeffStr}E${String(this._exponent)}`; } const intPart = absCoeffStr[0] ?? ''; const fracPart = absCoeffStr.slice(1); const adjustedExp = this._exponent + absCoeffStr.length - 1; return `${isNeg ? '-' : ''}${intPart}.${fracPart}E${String(adjustedExp)}`; } if (decimalPlaces >= absCoeffStr.length) { const leadingZeros = '0'.repeat(decimalPlaces - absCoeffStr.length); return `${isNeg ? '-' : ''}0.${leadingZeros}${absCoeffStr}`; } else { const integerPart = absCoeffStr.slice(0, absCoeffStr.length - decimalPlaces); const fractionalPart = absCoeffStr.slice(absCoeffStr.length - decimalPlaces); return `${isNeg ? '-' : ''}${integerPart}.${fractionalPart}`; } } const plainLength = absCoeffStr.length + this._exponent; if (plainLength <= PLAIN_NOTATION_DIGIT_LIMIT) { if (this._exponent === 0) { return coeffStr; } const trailingZeros = '0'.repeat(this._exponent); return `${isNeg ? '-' : ''}${absCoeffStr}${trailingZeros}`; } else { if (absCoeffStr.length === 1) { return `${coeffStr}E+${String(this._exponent)}`; } const integerPart = absCoeffStr[0] ?? ''; const fractionalPart = absCoeffStr.slice(1); const adjustedExponent = this._exponent + absCoeffStr.length - 1; return `${isNeg ? '-' : ''}${integerPart}.${fractionalPart}E+${String(adjustedExponent)}`; } } /** * 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() { return this.toString(); } /** * 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() { return Number(this.toString()); } /** * 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, direction) { if (decimalPlaces < 0 || !Number.isInteger(decimalPlaces)) { throw new Error('decimalPlaces must be a non-negative integer'); } const formatFixed = (coef) => { const coeffStr = coef.toString(); const isNeg = coeffStr.startsWith('-'); const absCoeffStr = isNeg ? coeffStr.slice(1) : coeffStr; if (decimalPlaces === 0) { return coeffStr; } if (decimalPlaces >= absCoeffStr.length) { const leadingZeros = '0'.repeat(decimalPlaces - absCoeffStr.length); return `${isNeg ? '-' : ''}0.${leadingZeros}${absCoeffStr}`; } else { const integerPart = absCoeffStr.slice(0, absCoeffStr.length - decimalPlaces); const fractionalPart = absCoeffStr.slice(absCoeffStr.length - decimalPlaces); return `${isNeg ? '-' : ''}${integerPart}.${fractionalPart}`; } }; const targetExponent = -decimalPlaces; if (this._exponent === targetExponent) { return formatFixed(this._coefficient); } if (this._exponent < targetExponent) { // Need to reduce precision — round the excess digits. const scaleDiff = targetExponent - this._exponent; const divisor = 10n ** BigInt(scaleDiff); const quotient = this._coefficient / divisor; const remainder = this._coefficient % divisor; const rounded = DecimalImpl.roundDivision(quotient, remainder, divisor, direction); return formatFixed(rounded); } else { // Need to increase precision — pad with trailing zeros. const scaleDiff = this._exponent - targetExponent; const scaled = this._coefficient * 10n ** BigInt(scaleDiff); return formatFixed(scaled); } } /** * 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() { return this.toString(); } } /** * 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 function isDecimal(value) { return value instanceof DecimalImpl; } /** * Companion object for creating {@link Decimal} instances. * * @public */ export 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) { if (typeof value === 'bigint') { return new DecimalImpl(value, 0); } if (typeof value === 'number') { if (!Number.isFinite(value)) { throw new Error('Number must be finite'); } return Decimal.from(value.toString()); } // Parse string. const trimmed = value.trim(); if (trimmed === '') { throw new Error('Cannot parse empty string as Decimal'); } // Match: optional sign, integer digits, optional fraction, optional exponent. const match = /^([+-]?)(\d+)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/.exec(trimmed); if (!match) { throw new Error(`Invalid decimal string: ${value}`); } const sign = match[1] === '-' ? -1n : 1n; const integerPart = match[2] ?? ''; const fractionalPart = match[3] ?? ''; const exponentPart = match[4] ? Number(match[4]) : 0; if (!Number.isSafeInteger(exponentPart) || exponentPart > MAX_EXPONENT || exponentPart < -MAX_EXPONENT) { throw new Error(`Exponent out of range: ${String(match[4])} exceeds safe integer bounds`); } const coefficientStr = integerPart + fractionalPart; const coefficient = sign * BigInt(coefficientStr); const exponent = exponentPart - fractionalPart.length; if (!Number.isSafeInteger(exponent) || exponent > MAX_EXPONENT || exponent < -MAX_EXPONENT) { throw new Error(`Computed exponent out of range: ${String(exponent)} exceeds safe integer bounds`); } return new DecimalImpl(coefficient, exponent); }, /** * The {@link Decimal} value representing zero. * * @remarks * Pre-allocated singleton — prefer `Decimal.zero` over * `Decimal.from(0)` to avoid an unnecessary allocation. * * @public */ zero: new DecimalImpl(0n, 0), }; //# sourceMappingURL=Decimal.js.map