From f7b095efabbc8da93de3b63f773756b467b6d3d9 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sun, 29 Oct 2023 02:24:51 +0700 Subject: [PATCH] fix handling of fractional number part and some special cases in `Number.fromString` --- CHANGELOG.md | 1 + .../modules/esnext.number.from-string.js | 19 ++++++++++++------- .../unit-global/esnext.number.from-string.js | 17 +++++++++++++++++ tests/unit-pure/esnext.number.from-string.js | 17 +++++++++++++++++ 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55b711b1ba9a..533bef5e1d73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Simplified `structuredClone` polyfill, avoided second tree pass in cases of transferring - Added support of `SuppressedError` to `structuredClone` polyfill - Removed unspecified unnecessary `ArrayBuffer` and `DataView` dependencies of `structuredClone` lack of which could cause errors in some entries in IE10- +- Fixed handling of fractional number part and some special cases in [`Number.fromString`](https://github.com/tc39/proposal-number-fromstring) - Compat data improvements: - Updated Opera Android 78 compat data mapping diff --git a/packages/core-js/modules/esnext.number.from-string.js b/packages/core-js/modules/esnext.number.from-string.js index d29c4c9c2782..49a5ba142bfd 100644 --- a/packages/core-js/modules/esnext.number.from-string.js +++ b/packages/core-js/modules/esnext.number.from-string.js @@ -9,30 +9,35 @@ var INVALID_RADIX = 'Invalid radix'; var $RangeError = RangeError; var $SyntaxError = SyntaxError; var $TypeError = TypeError; -var valid = /^[\da-z]+$/; +var pow = Math.pow; +var valid = /^[\d.a-z]+$/; var charAt = uncurryThis(''.charAt); var exec = uncurryThis(valid.exec); var numberToString = uncurryThis(1.0.toString); var stringSlice = uncurryThis(''.slice); +var split = uncurryThis(''.split); // `Number.fromString` method // https://github.com/tc39/proposal-number-fromstring $({ target: 'Number', stat: true, forced: true }, { fromString: function fromString(string, radix) { var sign = 1; - var R, mathNum; if (typeof string != 'string') throw new $TypeError(INVALID_NUMBER_REPRESENTATION); if (!string.length) throw new $SyntaxError(INVALID_NUMBER_REPRESENTATION); + if (string === 'NaN') return NaN; if (charAt(string, 0) === '-') { sign = -1; string = stringSlice(string, 1); - if (!string.length) throw new $SyntaxError(INVALID_NUMBER_REPRESENTATION); + if (!string.length || string === '0') throw new $SyntaxError(INVALID_NUMBER_REPRESENTATION); } - R = radix === undefined ? 10 : toIntegerOrInfinity(radix); + if (string === 'Infinity') return sign * Infinity; + var R = radix === undefined ? 10 : toIntegerOrInfinity(radix); if (R < 2 || R > 36) throw new $RangeError(INVALID_RADIX); - if (!exec(valid, string) || numberToString(mathNum = parseInt(string, R), R) !== string) { - throw new $SyntaxError(INVALID_NUMBER_REPRESENTATION); - } + if (!exec(valid, string)) throw new $SyntaxError(INVALID_NUMBER_REPRESENTATION); + var parts = split(string, '.'); + var mathNum = parseInt(parts[0], R); + if (parts.length > 1) mathNum += parseInt(parts[1], R) / pow(R, parts[1].length); + if (numberToString(mathNum, R) !== string) throw new $SyntaxError(INVALID_NUMBER_REPRESENTATION); return sign * mathNum; } }); diff --git a/tests/unit-global/esnext.number.from-string.js b/tests/unit-global/esnext.number.from-string.js index 4303700a64a5..fe65a08c985b 100644 --- a/tests/unit-global/esnext.number.from-string.js +++ b/tests/unit-global/esnext.number.from-string.js @@ -16,9 +16,26 @@ QUnit.test('Number.fromString', assert => { assert.same(fromString('10', radix), radix, `Radix ${ radix }`); } assert.throws(() => fromString('10', -4294967294), RangeError, 'Radix uses ToInteger #1'); + + assert.same(fromString('NaN'), NaN); + assert.same(fromString('NaN', 2), NaN); + assert.same(fromString('Infinity'), Infinity); + assert.same(fromString('Infinity', 2), Infinity); + assert.same(fromString('-Infinity'), -Infinity); + assert.same(fromString('-Infinity', 2), -Infinity); + assert.same(fromString('10', 2.5), 2, 'Radix uses ToInteger #2'); assert.same(fromString('42'), 42); assert.same(fromString('42', 10), 42); + assert.same(fromString('3.14159', 10), 3.14159); + assert.same(fromString('-100.11', 2), -4.75); + assert.same(fromString('202.1', 3), 20.333333333333332); + + assert.same(fromString('0'), 0); + assert.same(fromString('0', 2), 0); + assert.throws(() => fromString('-0'), SyntaxError); + assert.throws(() => fromString('-0', 2), SyntaxError); + assert.throws(() => fromString('0xc0ffee'), SyntaxError); assert.throws(() => fromString('0o755'), SyntaxError); assert.throws(() => fromString('0b00101010'), SyntaxError); diff --git a/tests/unit-pure/esnext.number.from-string.js b/tests/unit-pure/esnext.number.from-string.js index 6b13f958927e..a4d4d87e851e 100644 --- a/tests/unit-pure/esnext.number.from-string.js +++ b/tests/unit-pure/esnext.number.from-string.js @@ -15,9 +15,26 @@ QUnit.test('Number.fromString', assert => { assert.same(fromString('10', radix), radix, `Radix ${ radix }`); } assert.throws(() => fromString('10', -4294967294), RangeError, 'Radix uses ToInteger #1'); + + assert.same(fromString('NaN'), NaN); + assert.same(fromString('NaN', 2), NaN); + assert.same(fromString('Infinity'), Infinity); + assert.same(fromString('Infinity', 2), Infinity); + assert.same(fromString('-Infinity'), -Infinity); + assert.same(fromString('-Infinity', 2), -Infinity); + assert.same(fromString('10', 2.5), 2, 'Radix uses ToInteger #2'); assert.same(fromString('42'), 42); assert.same(fromString('42', 10), 42); + assert.same(fromString('3.14159', 10), 3.14159); + assert.same(fromString('-100.11', 2), -4.75); + assert.same(fromString('202.1', 3), 20.333333333333332); + + assert.same(fromString('0'), 0); + assert.same(fromString('0', 2), 0); + assert.throws(() => fromString('-0'), SyntaxError); + assert.throws(() => fromString('-0', 2), SyntaxError); + assert.throws(() => fromString('0xc0ffee'), SyntaxError); assert.throws(() => fromString('0o755'), SyntaxError); assert.throws(() => fromString('0b00101010'), SyntaxError);