Skip to content

Commit

Permalink
add Math.sumPrecise
Browse files Browse the repository at this point in the history
  • Loading branch information
zloirock committed Apr 15, 2024
1 parent 0ac334d commit 9da401f
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/core-js-compat/src/data.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2258,6 +2258,8 @@ export const data = {
},
'esnext.math.signbit': {
},
'esnext.math.sum-precise': {
},
// TODO: Remove from `core-js@4`
'esnext.math.umulh': {
},
Expand Down
1 change: 1 addition & 0 deletions packages/core-js-compat/src/modules-by-versions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ export default {
'es.set.is-superset-of.v2',
'es.set.symmetric-difference.v2',
'es.set.union.v2',
'esnext.math.sum-precise',
'web.url.parse',
],
};
2 changes: 2 additions & 0 deletions packages/core-js/full/math/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';
var parent = require('../../actual/math');
require('../../modules/es.array.iterator');
require('../../modules/esnext.math.clamp');
require('../../modules/esnext.math.deg-per-rad');
require('../../modules/esnext.math.degrees');
Expand All @@ -9,6 +10,7 @@ require('../../modules/esnext.math.radians');
require('../../modules/esnext.math.scale');
require('../../modules/esnext.math.seeded-prng');
require('../../modules/esnext.math.signbit');
require('../../modules/esnext.math.sum-precise');
// TODO: Remove from `core-js@4`
require('../../modules/esnext.math.iaddh');
require('../../modules/esnext.math.isubh');
Expand Down
6 changes: 6 additions & 0 deletions packages/core-js/full/math/sum-precise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use strict';
require('../../modules/es.array.iterator');
require('../../modules/esnext.math.sum-precise');
var path = require('../../internals/path');

module.exports = path.Math.sumPrecise;
162 changes: 162 additions & 0 deletions packages/core-js/modules/esnext.math.sum-precise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
'use strict';
// based on Shewchuk's algorithm for exactly floating point addition
// adapted from https://github.com/tc39/proposal-math-sum/blob/3513d58323a1ae25560e8700aa5294500c6c9287/polyfill/polyfill.mjs
var $ = require('../internals/export');
var uncurryThis = require('../internals/function-uncurry-this');
var iterate = require('../internals/iterate');

var $RangeError = RangeError;
var $TypeError = TypeError;
var $Infinity = Infinity;
var $NaN = NaN;
var abs = Math.abs;
var pow = Math.pow;
var push = uncurryThis([].push);

var POW_2_1023 = pow(2, 1023);
var MAX_SAFE_INTEGER = pow(2, 53) - 1;
// exponent 11111111110, significand all 1s
var MAX_DOUBLE = 1.79769313486231570815e+308; // 2 ** 1024 - 2 ** (1023 - 52)
// exponent 11111111110, significand all 1s except final 0
var PENULTIMATE_DOUBLE = 1.79769313486231550856e+308; // 2 ** 1024 - 2 * 2 ** (1023 - 52)
// exponent 11111001010, significand all 0s
var MAX_ULP = MAX_DOUBLE - PENULTIMATE_DOUBLE; // 1.99584030953471981166e+292, <- 2 ** (1023 - 52)

var NOT_A_NUMBER = {};
var MINUS_INFINITY = {};
var PLUS_INFINITY = {};
var MINUS_ZERO = {};
var FINITE = {};

// prerequisite: abs(x) >= abs(y)
var twosum = function (x, y) {
var hi = x + y;
var lo = y - (hi - x);
return { hi: hi, lo: lo };
};

// `Math.sumPrecise` method
// https://github.com/tc39/proposal-math-sum
$({ target: 'Math', stat: true, forced: true }, {
// eslint-disable-next-line max-statements -- ok
sumPrecise: function sumPrecise(items) {
var numbers = [];
var count = 0;
var state = MINUS_ZERO;

iterate(items, function (n) {
if (++count >= MAX_SAFE_INTEGER) throw new $RangeError('Maximum allowed index exceeded');
if (typeof n != 'number') throw new $TypeError('Value is not a number');
if (state !== NOT_A_NUMBER) {
// eslint-disable-next-line no-self-compare -- NaN check
if (n !== n) state = NOT_A_NUMBER;
else if (n === $Infinity) state = state === MINUS_INFINITY ? NOT_A_NUMBER : PLUS_INFINITY;
else if (n === -$Infinity) state = state === PLUS_INFINITY ? NOT_A_NUMBER : MINUS_INFINITY;
else if ((n !== 0 || (1 / n) === $Infinity) && (state === MINUS_ZERO || state === FINITE)) {
state = FINITE;
push(numbers, n);
}
}
});

switch (state) {
case NOT_A_NUMBER: return $NaN;
case MINUS_INFINITY: return -$Infinity;
case PLUS_INFINITY: return $Infinity;
case MINUS_ZERO: return -0;
}

var partials = [];
var overflow = 0; // conceptually 2**1024 times this value; the final partial is biased by this amount
var x, y, sum, hi, lo, tmp;

for (var i = 0; i < numbers.length; i++) {
x = numbers[i];
var actuallyUsedPartials = 0;
for (var j = 0; j < partials.length; j++) {
y = partials[j];
if (abs(x) < abs(y)) {
tmp = x;
x = y;
y = tmp;
}
sum = twosum(x, y);
hi = sum.hi;
lo = sum.lo;
if (abs(hi) === $Infinity) {
var sign = hi === $Infinity ? 1 : -1;
overflow += sign;

x = (x - (sign * POW_2_1023)) - (sign * POW_2_1023);
if (abs(x) < abs(y)) {
tmp = x;
x = y;
y = tmp;
}
sum = twosum(x, y);
hi = sum.hi;
lo = sum.lo;
}
if (lo !== 0) {
partials[actuallyUsedPartials] = lo;
actuallyUsedPartials += 1;
}
x = hi;
}
partials.length = actuallyUsedPartials;
if (x !== 0) partials[partials.length] = x;
}

// compute the exact sum of partials, stopping once we lose precision
var n = partials.length - 1;
hi = 0;
lo = 0;

if (overflow !== 0) {
var next = n >= 0 ? partials[n] : 0;
n -= 1;
if (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(abs(overflow) === 1)
sum = twosum(overflow * POW_2_1023, next / 2);
hi = sum.hi;
lo = sum.lo;
lo *= 2;
if (abs(2 * hi) === $Infinity) {
// rounding to the maximum value
if (hi > 0) {
return (hi === POW_2_1023 && lo === -(MAX_ULP / 2) && n >= 0 && partials[n] < 0) ? MAX_DOUBLE : $Infinity;
} return (hi === -POW_2_1023 && lo === (MAX_ULP / 2) && n >= 0 && partials[n] > 0) ? -MAX_DOUBLE : -$Infinity;
}

if (lo !== 0) {
partials[n + 1] = lo;
n += 1;
lo = 0;
}

hi *= 2;
}

while (n >= 0) {
x = hi;
y = partials[n];
n -= 1;
sum = twosum(x, y);
hi = sum.hi;
lo = sum.lo;
if (lo !== 0) break;
}

if (n >= 0 && ((lo < 0.0 && partials[n] < 0.0) || (lo > 0.0 && partials[n] > 0.0))) {
y = lo * 2.0;
x = hi + y;
if (y === x - hi) hi = x;
}

return hi;
}
});
3 changes: 3 additions & 0 deletions packages/core-js/proposals/math-sum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';
// https://github.com/tc39/proposal-math-sum
require('../modules/esnext.math.sum-precise');
1 change: 1 addition & 0 deletions packages/core-js/stage/2.7.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';
var parent = require('./3');

require('../proposals/math-sum');
require('../proposals/promise-try');

module.exports = parent;
3 changes: 3 additions & 0 deletions tests/compat/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -1767,6 +1767,9 @@ GLOBAL.tests = {
'esnext.math.signbit': function () {
return Math.signbit;
},
'esnext.math.sum-precise': function () {
return Math.sumPrecise;
},
'esnext.number.from-string': function () {
return Number.fromString;
},
Expand Down
2 changes: 2 additions & 0 deletions tests/entries/unit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,7 @@ for (PATH of ['core-js-pure', 'core-js']) {
ok(load(NS, 'math/scale')(3, 1, 2, 1, 2) === 3);
ok(typeof load(NS, 'math/seeded-prng')({ seed: 42 }).next().value === 'number');
ok(load(NS, 'math/signbit')(-2) === true);
ok(load(NS, 'math/sum-precise')([1, 2, 3]) === 6);
ok(load(NS, 'math/umulh')(0xFFFFFFFF, 7) === 6);
ok(load(NS, 'map/of')([1, 2], [3, 4]) instanceof Map);
ok(load(NS, 'map/reduce')(new Map([[1, 2], [2, 3], [3, 4]]), (a, b) => a + b) === 9);
Expand Down Expand Up @@ -938,6 +939,7 @@ for (PATH of ['core-js-pure', 'core-js']) {
load('proposals/map-upsert-stage-2');
load('proposals/math-extensions');
load('proposals/math-signbit');
load('proposals/math-sum');
load('proposals/number-from-string');
load('proposals/number-range');
load('proposals/object-from-entries');
Expand Down

0 comments on commit 9da401f

Please sign in to comment.