Skip to content

Commit 5f7f25e

Browse files
committed
improve accuracy of Number.prototype.toExponential polyfills with big and small values
1 parent fcd98d9 commit 5f7f25e

File tree

4 files changed

+34
-6
lines changed

4 files changed

+34
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- Throw a `RangeError` on `NaN` `start` / `end` / `step`
66
- Allow `null` as `optionOrStep`
77
- Improved accuracy of `Math.{ asinh, atanh }` polyfills with big and small values
8+
- Improved accuracy of `Number.prototype.toExponential` polyfills with big and small values
89
- Wrap `Symbol.for` in `Symbol.prototype.description` polyfill for correct handling of empty string descriptions
910
- Fixed one more case (`Iterator.prototype.take`) of a V8 ~ Chromium < 126 [bug](https://issues.chromium.org/issues/336839115)
1011
- Forced replacement of `Iterator.{ concat, zip, zipKeyed }` in the pure version for ensuring proper wrapped `Iterator` instances as the result

packages/core-js/modules/es.number.to-exponential.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ var nativeToExponential = uncurryThis(1.1.toExponential);
1818
var repeat = uncurryThis($repeat);
1919
var stringSlice = uncurryThis(''.slice);
2020

21+
var POW_10_308 = pow(10, 308);
22+
2123
// Edge 17-
2224
var ROUNDS_PROPERLY = nativeToExponential(-6.9e-11, 4) === '-6.9000e-11'
2325
// IE11- && Edge 14-
@@ -58,7 +60,7 @@ $({ target: 'Number', proto: true, forced: FORCED }, {
5860
if (f < 0 || f > 20) throw new $RangeError('Incorrect fraction digits');
5961
if (ROUNDS_PROPERLY) return nativeToExponential(x, f);
6062
var s = '';
61-
var m, e, c, d;
63+
var m, e, c, d, l, n, xScaled;
6264
if (x < 0) {
6365
s = '-';
6466
x = -x;
@@ -67,13 +69,20 @@ $({ target: 'Number', proto: true, forced: FORCED }, {
6769
e = 0;
6870
m = repeat('0', f + 1);
6971
} else {
70-
// this block is based on https://gist.github.com/SheetJSDev/1100ad56b9f856c95299ed0e068eea08
7172
// TODO: improve accuracy with big fraction digits
72-
var l = log10(x);
73+
l = log10(x);
7374
e = floor(l);
74-
var w = pow(10, e - f);
75-
var n = round(x / w);
76-
if (2 * x >= (2 * n + 1) * w) {
75+
// compute x / pow(10, e - f) and round, avoiding underflow/overflow
76+
if (f - e >= 308) {
77+
// pow(10, e - f) would underflow to a subnormal or zero; split computation
78+
xScaled = x * POW_10_308 * pow(10, f - e - 308);
79+
} else {
80+
xScaled = x / pow(10, e - f);
81+
}
82+
n = round(xScaled);
83+
// correct tie-breaking: round half up
84+
// avoids `2 * x` overflow for values near MAX_VALUE
85+
if (xScaled - n >= 0.5) {
7786
n += 1;
7887
}
7988
if (n >= pow(10, f + 1)) {

tests/unit-global/es.number.to-exponential.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,15 @@ QUnit.test('Number#toExponential', assert => {
126126
assert.same(toExponential.call(-0, 7), '0.0000000e+0', '-0 and 7');
127127
assert.same(toExponential.call(-0, 20), '0.00000000000000000000e+0', '-0 and 20');
128128

129+
// overflow / underflow edge cases
130+
assert.same(toExponential.call(9e307, 0), '9e+307', '9e307, 0');
131+
assert.same(toExponential.call(-9e307, 0), '-9e+307', '-9e307, 0');
132+
assert.same(toExponential.call(Number.MAX_VALUE, 0), '2e+308', 'MAX_VALUE, 0');
133+
assert.same(toExponential.call(Number.MAX_VALUE, 5), '1.79769e+308', 'MAX_VALUE, 5');
134+
assert.same(toExponential.call(Number.MIN_VALUE, 0), '5e-324', 'MIN_VALUE, 0');
135+
assert.same(toExponential.call(Number.MIN_VALUE, 1), '4.9e-324', 'MIN_VALUE, 1');
136+
assert.same(toExponential.call(1e-323, 0), '1e-323', '1e-323, 0');
137+
129138
assert.same(toExponential.call(NaN, 1000), 'NaN', 'NaN check before fractionDigits check');
130139
assert.same(toExponential.call(Infinity, 1000), 'Infinity', 'Infinity check before fractionDigits check');
131140
assert.notThrows(() => toExponential.call(new Number(1e21), -0.1) === '1e+21');

tests/unit-pure/es.number.to-exponential.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,15 @@ QUnit.test('Number#toExponential', assert => {
122122
assert.same(toExponential.call(-0, 7), '0.0000000e+0', '-0 and 7');
123123
assert.same(toExponential.call(-0, 20), '0.00000000000000000000e+0', '-0 and 20');
124124

125+
// overflow / underflow edge cases
126+
assert.same(toExponential.call(9e307, 0), '9e+307', '9e307, 0');
127+
assert.same(toExponential.call(-9e307, 0), '-9e+307', '-9e307, 0');
128+
assert.same(toExponential.call(Number.MAX_VALUE, 0), '2e+308', 'MAX_VALUE, 0');
129+
assert.same(toExponential.call(Number.MAX_VALUE, 5), '1.79769e+308', 'MAX_VALUE, 5');
130+
assert.same(toExponential.call(Number.MIN_VALUE, 0), '5e-324', 'MIN_VALUE, 0');
131+
assert.same(toExponential.call(Number.MIN_VALUE, 1), '4.9e-324', 'MIN_VALUE, 1');
132+
assert.same(toExponential.call(1e-323, 0), '1e-323', '1e-323, 0');
133+
125134
assert.same(toExponential.call(NaN, 1000), 'NaN', 'NaN check before fractionDigits check');
126135
assert.same(toExponential.call(Infinity, 1000), 'Infinity', 'Infinity check before fractionDigits check');
127136
assert.notThrows(() => toExponential.call(new Number(1e21), -0.1) === '1e+21');

0 commit comments

Comments
 (0)