UNPKG

xen-dev-utils

Version:

Utility functions used by the Scale Workshop ecosystem

181 lines 6.74 kB
"use strict"; // Stolen from: https://github.com/tc39/proposal-math-sum/blob/main/polyfill/polyfill.mjs // Linted and type-annotated by Lumi Pakkanen. Object.defineProperty(exports, "__esModule", { value: true }); exports.sum = void 0; /* https://www-2.cs.cmu.edu/afs/cs/project/quake/public/papers/robust-arithmetic.ps Shewchuk's algorithm for exactly floating point addition as implemented in Python's fsum: https://github.com/python/cpython/blob/48dfd74a9db9d4aa9c6f23b4a67b461e5d977173/Modules/mathmodule.c#L1359-L1474 adapted to handle overflow via an additional "biased" partial, representing 2**1024 times its actual value */ // exponent 11111111110, significand all 1s const MAX_DOUBLE = 1.79769313486231570815e308; // i.e. (2**1024 - 2**(1023 - 52)) // exponent 11111111110, significand all 1s except final 0 const PENULTIMATE_DOUBLE = 1.79769313486231550856e308; // i.e. (2**1024 - 2 * 2**(1023 - 52)) // exponent 11111001010, significand all 0s const MAX_ULP = MAX_DOUBLE - PENULTIMATE_DOUBLE; // 1.99584030953471981166e+292, i.e. 2**(1023 - 52) // prerequisite: Math.abs(x) >= Math.abs(y) function twosum(x, y) { const hi = x + y; const lo = y - (hi - x); return { hi, lo }; } /** * Accurately add up elements from an iterable using Shewchuk's algorithm. * @param iterable Numbers to sum together. * @returns The sum of the elements. */ function sum(iterable) { const partials = []; let overflow = 0; // conceptually 2**1024 times this value; the final partial // for purposes of the polyfill we're going to ignore closing the iterator, sorry const iterator = iterable[Symbol.iterator](); const next = iterator.next.bind(iterator); // in C this would be done using a goto function drainNonFiniteValue(current) { while (true) { const { done, value } = next(); if (done) { return current; } if (!Number.isFinite(value)) { // summing any distinct two of the three non-finite values gives NaN // summing any one of them with itself gives itself if (!Object.is(value, current)) { current = NaN; } } } } // handle list of -0 special case while (true) { const { done, value } = next(); if (done) { return -0; } if (!Object.is(value, -0)) { if (!Number.isFinite(value)) { return drainNonFiniteValue(value); } partials.push(value); break; } } // main loop while (true) { const { done, value } = next(); if (done) { break; } let x = +value; if (!Number.isFinite(x)) { return drainNonFiniteValue(x); } // we're updating partials in place, but it is maybe easier to understand if you think of it as making a new copy let actuallyUsedPartials = 0; // let newPartials = []; for (let y of partials) { if (Math.abs(x) < Math.abs(y)) { [x, y] = [y, x]; } let { hi, lo } = twosum(x, y); if (Math.abs(hi) === Infinity) { const sign = hi === Infinity ? 1 : -1; overflow += sign; if (Math.abs(overflow) >= 2 ** 53) { throw new RangeError('overflow'); } x = x - sign * 2 ** 1023 - sign * 2 ** 1023; if (Math.abs(x) < Math.abs(y)) { [x, y] = [y, x]; } ({ hi, lo } = twosum(x, y)); } if (lo !== 0) { partials[actuallyUsedPartials] = lo; ++actuallyUsedPartials; // newPartials.push(lo); } x = hi; } partials.length = actuallyUsedPartials; // assert.deepStrictEqual(partials, newPartials) // partials = newPartials if (x !== 0) { partials.push(x); } } // compute the exact sum of partials, stopping once we lose precision let n = partials.length - 1; let hi = 0; let lo = 0; if (overflow !== 0) { const next = n >= 0 ? partials[n] : 0; --n; if (Math.abs(overflow) > 1 || (overflow > 0 && next > 0) || (overflow < 0 && next < 0)) { return overflow > 0 ? Infinity : -Infinity; } // here we actually have to do the arithmetic // drop a factor of 2 so we can do it without overflow // assert(Math.abs(overflow) === 1) ({ hi, lo } = twosum(overflow * 2 ** 1023, next / 2)); lo *= 2; if (Math.abs(2 * hi) === Infinity) { // stupid edge case: rounding to the maximum value // MAX_DOUBLE has a 1 in the last place of its significand, so if we subtract exactly half a ULP from 2**1024, the result rounds away from it (i.e. to infinity) under ties-to-even // but if the next partial has the opposite sign of the current value, we need to round towards MAX_DOUBLE instead // this is the same as the "handle rounding" case below, but there's only one potentially-finite case we need to worry about, so we just hardcode that one if (hi > 0) { if (hi === 2 ** 1023 && lo === -(MAX_ULP / 2) && n >= 0 && partials[n] < 0) { return MAX_DOUBLE; } return Infinity; } else { if (hi === -(2 ** 1023) && lo === MAX_ULP / 2 && n >= 0 && partials[n] > 0) { return -MAX_DOUBLE; } return -Infinity; } } if (lo !== 0) { partials[n + 1] = lo; ++n; lo = 0; } hi *= 2; } while (n >= 0) { const x = hi; const y = partials[n]; --n; // assert: Math.abs(x) > Math.abs(y) ({ hi, lo } = twosum(x, y)); if (lo !== 0) { break; } } // handle rounding // when the roundoff error is exactly half of the ULP for the result, we need to check one more partial to know which way to round if (n >= 0 && ((lo < 0.0 && partials[n] < 0.0) || (lo > 0.0 && partials[n] > 0.0))) { const y = lo * 2.0; const x = hi + y; const yr = x - hi; if (y === yr) { hi = x; } } return hi; } exports.sum = sum; //# sourceMappingURL=sum-precise.js.map