fix: bound money conversion after cents scaling
This commit is contained in:
+10
-7
@@ -7,8 +7,9 @@
|
||||
export type IskLike = string | bigint | number;
|
||||
|
||||
const INTEGER_TEXT = /^-?\d+$/;
|
||||
// The final lookahead makes the end assertion strict: JavaScript's `$` alone
|
||||
// would also accept a match immediately before one trailing newline.
|
||||
// The final lookahead makes the end assertion strict. This intentionally
|
||||
// tightens the old ESI regex, because JavaScript's `$` alone also accepts a
|
||||
// match immediately before one trailing newline.
|
||||
const DECIMAL_TEXT = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$(?![\s\S])/;
|
||||
|
||||
/** Resource bounds for signed decimal ingestion. These reject excess input; no truncation is performed. */
|
||||
@@ -68,18 +69,20 @@ export function decimalIskToCents(value: string): string {
|
||||
}
|
||||
|
||||
const exponent = exponentText === undefined ? 0n : parseDecimalExponent(exponentText);
|
||||
const decimalPlaces = BigInt(fraction.length) - exponent;
|
||||
if (decimalPlaces < -MAX_DECIMAL_SHIFT || decimalPlaces > MAX_DECIMAL_SHIFT) {
|
||||
// Convert the decimal coefficient to cents before applying the resource
|
||||
// bound: value * 10^(exponent - fractionDigits) * 100.
|
||||
const centsShift = exponent + 2n - BigInt(fraction.length);
|
||||
if (centsShift < -MAX_DECIMAL_SHIFT || centsShift > MAX_DECIMAL_SHIFT) {
|
||||
throw new RangeError('ISK decimal shift exceeds resource bounds');
|
||||
}
|
||||
|
||||
const digits = `${whole}${fraction}`.replace(/^0+(?=\d)/, '');
|
||||
const significand = BigInt(digits);
|
||||
let magnitude: bigint;
|
||||
if (decimalPlaces <= 2n) {
|
||||
magnitude = significand * 10n ** (2n - decimalPlaces);
|
||||
if (centsShift >= 0n) {
|
||||
magnitude = significand * 10n ** centsShift;
|
||||
} else {
|
||||
const divisor = 10n ** (decimalPlaces - 2n);
|
||||
const divisor = 10n ** -centsShift;
|
||||
const quotient = significand / divisor;
|
||||
const remainder = significand % divisor;
|
||||
// Half-away-from-zero is half-up for this non-negative magnitude.
|
||||
|
||||
@@ -37,9 +37,12 @@ describe('exact decimal ISK conversion', () => {
|
||||
expect(() => decimalIskToCents(`1e${'0'.repeat(8191)}`)).toThrow('input exceeds resource bounds');
|
||||
expect(() => decimalIskToCents('1e1001')).toThrow('exponent is outside bounds');
|
||||
expect(() => decimalIskToCents('1e-1001')).toThrow('exponent is outside bounds');
|
||||
expect(decimalIskToCents('1e1000')).toBe(`1${'0'.repeat(1002)}`);
|
||||
expect(decimalIskToCents('1e998')).toBe(`1${'0'.repeat(1000)}`);
|
||||
expect(() => decimalIskToCents('1e999')).toThrow('shift exceeds resource bounds');
|
||||
expect(() => decimalIskToCents('1e1000')).toThrow('shift exceeds resource bounds');
|
||||
expect(decimalIskToCents(`1.${'0'.repeat(1002)}`)).toBe('100');
|
||||
expect(() => decimalIskToCents(`1.${'0'.repeat(1003)}`)).toThrow('shift exceeds resource bounds');
|
||||
expect(decimalIskToCents(`0.${'0'.repeat(999)}1`)).toBe('0');
|
||||
expect(() => decimalIskToCents(`0.${'0'.repeat(1000)}1`)).toThrow('shift exceeds resource bounds');
|
||||
expect(decimalIskToCents('1'.repeat(4096))).toBe(`${BigInt('1'.repeat(4096)) * 100n}`);
|
||||
expect(() => decimalIskToCents('1'.repeat(4097))).toThrow('coefficient exceeds resource bounds');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user