fix: bound money conversion after cents scaling
This commit is contained in:
+10
-7
@@ -7,8 +7,9 @@
|
|||||||
export type IskLike = string | bigint | number;
|
export type IskLike = string | bigint | number;
|
||||||
|
|
||||||
const INTEGER_TEXT = /^-?\d+$/;
|
const INTEGER_TEXT = /^-?\d+$/;
|
||||||
// The final lookahead makes the end assertion strict: JavaScript's `$` alone
|
// The final lookahead makes the end assertion strict. This intentionally
|
||||||
// would also accept a match immediately before one trailing newline.
|
// 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])/;
|
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. */
|
/** 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 exponent = exponentText === undefined ? 0n : parseDecimalExponent(exponentText);
|
||||||
const decimalPlaces = BigInt(fraction.length) - exponent;
|
// Convert the decimal coefficient to cents before applying the resource
|
||||||
if (decimalPlaces < -MAX_DECIMAL_SHIFT || decimalPlaces > MAX_DECIMAL_SHIFT) {
|
// 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');
|
throw new RangeError('ISK decimal shift exceeds resource bounds');
|
||||||
}
|
}
|
||||||
|
|
||||||
const digits = `${whole}${fraction}`.replace(/^0+(?=\d)/, '');
|
const digits = `${whole}${fraction}`.replace(/^0+(?=\d)/, '');
|
||||||
const significand = BigInt(digits);
|
const significand = BigInt(digits);
|
||||||
let magnitude: bigint;
|
let magnitude: bigint;
|
||||||
if (decimalPlaces <= 2n) {
|
if (centsShift >= 0n) {
|
||||||
magnitude = significand * 10n ** (2n - decimalPlaces);
|
magnitude = significand * 10n ** centsShift;
|
||||||
} else {
|
} else {
|
||||||
const divisor = 10n ** (decimalPlaces - 2n);
|
const divisor = 10n ** -centsShift;
|
||||||
const quotient = significand / divisor;
|
const quotient = significand / divisor;
|
||||||
const remainder = significand % divisor;
|
const remainder = significand % divisor;
|
||||||
// Half-away-from-zero is half-up for this non-negative magnitude.
|
// 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(`1e${'0'.repeat(8191)}`)).toThrow('input exceeds resource bounds');
|
||||||
expect(() => decimalIskToCents('1e1001')).toThrow('exponent is outside bounds');
|
expect(() => decimalIskToCents('1e1001')).toThrow('exponent is outside bounds');
|
||||||
expect(() => decimalIskToCents('1e-1001')).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(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(4096))).toBe(`${BigInt('1'.repeat(4096)) * 100n}`);
|
||||||
expect(() => decimalIskToCents('1'.repeat(4097))).toThrow('coefficient exceeds resource bounds');
|
expect(() => decimalIskToCents('1'.repeat(4097))).toThrow('coefficient exceeds resource bounds');
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user