export const DECIMAL_RADIX = 10n; const SIGN_LENGTH = 1; const NEGATIVE_SIGN = -1n; const POSITIVE_SIGN = 1n; const ZERO_UNITS = 0n; const EQUAL = 0; const GREATER_THAN = 1; const LESS_THAN = -1; const INTEGER_SCALE = 0; const MIN_WHOLE_DIGITS = 1; const DIGITS_START = 0; const PREVIOUS_DIGIT_OFFSET = 1; const DIGIT_STEP = 1; const ROUNDING_DIVISOR = 2n; export type Decimal = { scale: number; value: bigint }; export function decimalFromString(value: string): Decimal { // eslint-disable-next-line security/detect-unsafe-regex -- Anchored decimal grammar: the literal decimal point separates disjoint digit runs; backtracking is linear. if (!/^-?(?:0|[1-9]\d*)(?:\.\d+)?$/.test(value)) { throw new Error('Invalid decimal string'); } const negative = value.startsWith('-'); const [whole, fraction = ''] = (negative ? value.slice(SIGN_LENGTH) : value).split('.'); return { value: (negative ? NEGATIVE_SIGN : POSITIVE_SIGN) * BigInt(`${whole}${fraction}`), scale: fraction.length, }; } export function add(left: Decimal, right: Decimal): Decimal { const scale = Math.max(left.scale, right.scale); return { value: left.value * DECIMAL_RADIX ** BigInt(scale - left.scale) + right.value * DECIMAL_RADIX ** BigInt(scale - right.scale), scale, }; } export function subtract(left: Decimal, right: Decimal): Decimal { return add(left, { value: -right.value, scale: right.scale }); } export function multiply(left: Decimal, right: Decimal): Decimal { return { value: left.value * right.value, scale: left.scale + right.scale }; } export function isZero(value: Decimal): boolean { return value.value === ZERO_UNITS; } export function compare(left: Decimal, right: Decimal): number { const difference = subtract(left, right).value; return difference === ZERO_UNITS ? EQUAL : difference > ZERO_UNITS ? GREATER_THAN : LESS_THAN; } export function formatDecimal({ value, scale }: Decimal): string { if (value === ZERO_UNITS) return '0'; const sign = value < ZERO_UNITS ? '-' : ''; if (scale === INTEGER_SCALE) return `${sign}${value < ZERO_UNITS ? -value : value}`; const digits = (value < ZERO_UNITS ? -value : value) .toString() .padStart(scale + MIN_WHOLE_DIGITS, '0'); const whole = digits.slice(DIGITS_START, -scale) || '0'; const rawFraction = digits.slice(-scale); let fractionEnd = rawFraction.length; while (rawFraction.charAt(fractionEnd - PREVIOUS_DIGIT_OFFSET) === '0') fractionEnd -= DIGIT_STEP; const fraction = rawFraction.slice(DIGITS_START, fractionEnd); const fractionalPart = fraction ? '.' + fraction : ''; return `${sign}${whole}${fractionalPart}`; } // Round half away from zero; callers choose presentation precision explicitly. export function divide(left: Decimal, right: Decimal, scale: number): Decimal { if (right.value === ZERO_UNITS) throw new Error('Division by zero'); const numerator = left.value * DECIMAL_RADIX ** BigInt(right.scale + scale); const denominator = right.value * DECIMAL_RADIX ** BigInt(left.scale); const sign = (numerator < ZERO_UNITS) !== (denominator < ZERO_UNITS) ? NEGATIVE_SIGN : POSITIVE_SIGN; const n = numerator < ZERO_UNITS ? -numerator : numerator; const d = denominator < ZERO_UNITS ? -denominator : denominator; return { value: sign * ((n + d / ROUNDING_DIVISOR) / d), scale }; }