Skip to content

Commit 9da401f

Browse files
committed
1 parent 0ac334d commit 9da401f

File tree

9 files changed

+182
-0
lines changed

9 files changed

+182
-0
lines changed

packages/core-js-compat/src/data.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2258,6 +2258,8 @@ export const data = {
22582258
},
22592259
'esnext.math.signbit': {
22602260
},
2261+
'esnext.math.sum-precise': {
2262+
},
22612263
// TODO: Remove from `core-js@4`
22622264
'esnext.math.umulh': {
22632265
},

packages/core-js-compat/src/modules-by-versions.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ export default {
245245
'es.set.is-superset-of.v2',
246246
'es.set.symmetric-difference.v2',
247247
'es.set.union.v2',
248+
'esnext.math.sum-precise',
248249
'web.url.parse',
249250
],
250251
};

packages/core-js/full/math/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22
var parent = require('../../actual/math');
3+
require('../../modules/es.array.iterator');
34
require('../../modules/esnext.math.clamp');
45
require('../../modules/esnext.math.deg-per-rad');
56
require('../../modules/esnext.math.degrees');
@@ -9,6 +10,7 @@ require('../../modules/esnext.math.radians');
910
require('../../modules/esnext.math.scale');
1011
require('../../modules/esnext.math.seeded-prng');
1112
require('../../modules/esnext.math.signbit');
13+
require('../../modules/esnext.math.sum-precise');
1214
// TODO: Remove from `core-js@4`
1315
require('../../modules/esnext.math.iaddh');
1416
require('../../modules/esnext.math.isubh');
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
'use strict';
2+
require('../../modules/es.array.iterator');
3+
require('../../modules/esnext.math.sum-precise');
4+
var path = require('../../internals/path');
5+
6+
module.exports = path.Math.sumPrecise;
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
'use strict';
2+
// based on Shewchuk's algorithm for exactly floating point addition
3+
// adapted from https://github.com/tc39/proposal-math-sum/blob/3513d58323a1ae25560e8700aa5294500c6c9287/polyfill/polyfill.mjs
4+
var $ = require('../internals/export');
5+
var uncurryThis = require('../internals/function-uncurry-this');
6+
var iterate = require('../internals/iterate');
7+
8+
var $RangeError = RangeError;
9+
var $TypeError = TypeError;
10+
var $Infinity = Infinity;
11+
var $NaN = NaN;
12+
var abs = Math.abs;
13+
var pow = Math.pow;
14+
var push = uncurryThis([].push);
15+
16+
var POW_2_1023 = pow(2, 1023);
17+
var MAX_SAFE_INTEGER = pow(2, 53) - 1;
18+
// exponent 11111111110, significand all 1s
19+
var MAX_DOUBLE = 1.79769313486231570815e+308; // 2 ** 1024 - 2 ** (1023 - 52)
20+
// exponent 11111111110, significand all 1s except final 0
21+
var PENULTIMATE_DOUBLE = 1.79769313486231550856e+308; // 2 ** 1024 - 2 * 2 ** (1023 - 52)
22+
// exponent 11111001010, significand all 0s
23+
var MAX_ULP = MAX_DOUBLE - PENULTIMATE_DOUBLE; // 1.99584030953471981166e+292, <- 2 ** (1023 - 52)
24+
25+
var NOT_A_NUMBER = {};
26+
var MINUS_INFINITY = {};
27+
var PLUS_INFINITY = {};
28+
var MINUS_ZERO = {};
29+
var FINITE = {};
30+
31+
// prerequisite: abs(x) >= abs(y)
32+
var twosum = function (x, y) {
33+
var hi = x + y;
34+
var lo = y - (hi - x);
35+
return { hi: hi, lo: lo };
36+
};
37+
38+
// `Math.sumPrecise` method
39+
// https://github.com/tc39/proposal-math-sum
40+
$({ target: 'Math', stat: true, forced: true }, {
41+
// eslint-disable-next-line max-statements -- ok
42+
sumPrecise: function sumPrecise(items) {
43+
var numbers = [];
44+
var count = 0;
45+
var state = MINUS_ZERO;
46+
47+
iterate(items, function (n) {
48+
if (++count >= MAX_SAFE_INTEGER) throw new $RangeError('Maximum allowed index exceeded');
49+
if (typeof n != 'number') throw new $TypeError('Value is not a number');
50+
if (state !== NOT_A_NUMBER) {
51+
// eslint-disable-next-line no-self-compare -- NaN check
52+
if (n !== n) state = NOT_A_NUMBER;
53+
else if (n === $Infinity) state = state === MINUS_INFINITY ? NOT_A_NUMBER : PLUS_INFINITY;
54+
else if (n === -$Infinity) state = state === PLUS_INFINITY ? NOT_A_NUMBER : MINUS_INFINITY;
55+
else if ((n !== 0 || (1 / n) === $Infinity) && (state === MINUS_ZERO || state === FINITE)) {
56+
state = FINITE;
57+
push(numbers, n);
58+
}
59+
}
60+
});
61+
62+
switch (state) {
63+
case NOT_A_NUMBER: return $NaN;
64+
case MINUS_INFINITY: return -$Infinity;
65+
case PLUS_INFINITY: return $Infinity;
66+
case MINUS_ZERO: return -0;
67+
}
68+
69+
var partials = [];
70+
var overflow = 0; // conceptually 2**1024 times this value; the final partial is biased by this amount
71+
var x, y, sum, hi, lo, tmp;
72+
73+
for (var i = 0; i < numbers.length; i++) {
74+
x = numbers[i];
75+
var actuallyUsedPartials = 0;
76+
for (var j = 0; j < partials.length; j++) {
77+
y = partials[j];
78+
if (abs(x) < abs(y)) {
79+
tmp = x;
80+
x = y;
81+
y = tmp;
82+
}
83+
sum = twosum(x, y);
84+
hi = sum.hi;
85+
lo = sum.lo;
86+
if (abs(hi) === $Infinity) {
87+
var sign = hi === $Infinity ? 1 : -1;
88+
overflow += sign;
89+
90+
x = (x - (sign * POW_2_1023)) - (sign * POW_2_1023);
91+
if (abs(x) < abs(y)) {
92+
tmp = x;
93+
x = y;
94+
y = tmp;
95+
}
96+
sum = twosum(x, y);
97+
hi = sum.hi;
98+
lo = sum.lo;
99+
}
100+
if (lo !== 0) {
101+
partials[actuallyUsedPartials] = lo;
102+
actuallyUsedPartials += 1;
103+
}
104+
x = hi;
105+
}
106+
partials.length = actuallyUsedPartials;
107+
if (x !== 0) partials[partials.length] = x;
108+
}
109+
110+
// compute the exact sum of partials, stopping once we lose precision
111+
var n = partials.length - 1;
112+
hi = 0;
113+
lo = 0;
114+
115+
if (overflow !== 0) {
116+
var next = n >= 0 ? partials[n] : 0;
117+
n -= 1;
118+
if (abs(overflow) > 1 || (overflow > 0 && next > 0) || (overflow < 0 && next < 0)) {
119+
return overflow > 0 ? $Infinity : -$Infinity;
120+
}
121+
// here we actually have to do the arithmetic
122+
// drop a factor of 2 so we can do it without overflow
123+
// assert(abs(overflow) === 1)
124+
sum = twosum(overflow * POW_2_1023, next / 2);
125+
hi = sum.hi;
126+
lo = sum.lo;
127+
lo *= 2;
128+
if (abs(2 * hi) === $Infinity) {
129+
// rounding to the maximum value
130+
if (hi > 0) {
131+
return (hi === POW_2_1023 && lo === -(MAX_ULP / 2) && n >= 0 && partials[n] < 0) ? MAX_DOUBLE : $Infinity;
132+
} return (hi === -POW_2_1023 && lo === (MAX_ULP / 2) && n >= 0 && partials[n] > 0) ? -MAX_DOUBLE : -$Infinity;
133+
}
134+
135+
if (lo !== 0) {
136+
partials[n + 1] = lo;
137+
n += 1;
138+
lo = 0;
139+
}
140+
141+
hi *= 2;
142+
}
143+
144+
while (n >= 0) {
145+
x = hi;
146+
y = partials[n];
147+
n -= 1;
148+
sum = twosum(x, y);
149+
hi = sum.hi;
150+
lo = sum.lo;
151+
if (lo !== 0) break;
152+
}
153+
154+
if (n >= 0 && ((lo < 0.0 && partials[n] < 0.0) || (lo > 0.0 && partials[n] > 0.0))) {
155+
y = lo * 2.0;
156+
x = hi + y;
157+
if (y === x - hi) hi = x;
158+
}
159+
160+
return hi;
161+
}
162+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
'use strict';
2+
// https://github.com/tc39/proposal-math-sum
3+
require('../modules/esnext.math.sum-precise');

packages/core-js/stage/2.7.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22
var parent = require('./3');
33

4+
require('../proposals/math-sum');
45
require('../proposals/promise-try');
56

67
module.exports = parent;

tests/compat/tests.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1767,6 +1767,9 @@ GLOBAL.tests = {
17671767
'esnext.math.signbit': function () {
17681768
return Math.signbit;
17691769
},
1770+
'esnext.math.sum-precise': function () {
1771+
return Math.sumPrecise;
1772+
},
17701773
'esnext.number.from-string': function () {
17711774
return Number.fromString;
17721775
},

tests/entries/unit.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,7 @@ for (PATH of ['core-js-pure', 'core-js']) {
796796
ok(load(NS, 'math/scale')(3, 1, 2, 1, 2) === 3);
797797
ok(typeof load(NS, 'math/seeded-prng')({ seed: 42 }).next().value === 'number');
798798
ok(load(NS, 'math/signbit')(-2) === true);
799+
ok(load(NS, 'math/sum-precise')([1, 2, 3]) === 6);
799800
ok(load(NS, 'math/umulh')(0xFFFFFFFF, 7) === 6);
800801
ok(load(NS, 'map/of')([1, 2], [3, 4]) instanceof Map);
801802
ok(load(NS, 'map/reduce')(new Map([[1, 2], [2, 3], [3, 4]]), (a, b) => a + b) === 9);
@@ -938,6 +939,7 @@ for (PATH of ['core-js-pure', 'core-js']) {
938939
load('proposals/map-upsert-stage-2');
939940
load('proposals/math-extensions');
940941
load('proposals/math-signbit');
942+
load('proposals/math-sum');
941943
load('proposals/number-from-string');
942944
load('proposals/number-range');
943945
load('proposals/object-from-entries');

0 commit comments

Comments
 (0)