Prove 32-bit products exact only below 2^53

The exactness test for a product's 32-bit form multiplied the operands'
magnitude bounds in a double and accepted a result of at most 2^53. A
bound of 2^53 + 1 (3 * 3002399751580331) rounds to 2^53 there, so a
product the double rounds took the integer multiply's low bits:
`(3 * 3002399751580331) | 0` printed 1 where Node prints 0. The test is
now strict: a bound below 2^53 is exact, and no bound of 2^53 or more
rounds below it.
This commit is contained in:
Chris Tate
2026-10-24 15:58:14 -05:00
parent 56b9158de2
commit ebeec4818d
3 changed files with 36 additions and 3 deletions
@@ -176,12 +176,14 @@ export function emitWrappingSum(
}
/** Whether every product of a value in `left` and one in `right` is an
* integer of magnitude at most 2^53, which the double product holds
* exactly. */
* integer of magnitude below 2^53, which the double product holds exactly.
* The bound's own product is computed in a double: a bound of at least
* 2^53 never rounds below 2^53, while 2^53 + 1 (3 * 3002399751580331)
* rounds to 2^53, so the test is strict. */
function exactProduct(left: IntegerRange, right: IntegerRange): boolean {
const magnitude = (range: IntegerRange): number =>
Math.max(Math.abs(range.min), Math.abs(range.max));
return magnitude(left) * magnitude(right) <= 2 ** 53;
return magnitude(left) * magnitude(right) < 2 ** 53;
}
/** The number locals of `fn` that only hold exact integers (never -0) or NaN, with the range of
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/8195-wrapping-products-near-2-53.ts"
],
"diags": []
}
@@ -0,0 +1,25 @@
// Products that reach bitwise operators with operands whose ranges are
// known, around 2^53: below it the double product is exact, and from it on
// the double product rounds, so the 32-bit result comes from the rounded
// value (3 * 3002399751580331 is 2^53 + 1, which rounds to 2^53).
const pairs: [number, number][] = [
[3, 3002399751580331],
[-3, 3002399751580331],
[2 ** 26, 2 ** 27],
[94906265, 94906267],
[94906267, 94906267],
[7, 1286742750677284],
[65535, 137443147777],
];
for (const [a, b] of pairs) console.log(a, b, (a * b) | 0, (a * b) >>> 0, (a * b) ^ 1, (a * b) << 3);
// The same products with literal operands, which the compiler sees whole.
console.log((3 * 3002399751580331) | 0, (3 * 3002399751580331) >>> 0);
console.log((-3 * 3002399751580331) | 0, (2 ** 26 * 2 ** 27) | 0);
console.log((94906267 * 94906267) | 0, (94906265 * 94906267) >>> 0);
// A counted loop whose counter multiplies a constant past 2^53.
const step = 1286742750677284;
let h = 0;
for (let k = 5; k < 9; k++) h = (h + ((k * step) | 0)) | 0;
console.log(h);