From dff58fd7f5648ae8e3f3a686fe4c5322f660baa2 Mon Sep 17 00:00:00 2001 From: sangwook Date: Wed, 6 Aug 2025 23:53:08 +0900 Subject: [PATCH 1/3] util: fix numericSeparator with negative fractional numbers Fix util.inspect() formatting bug where negative fractional numbers between -1 and 0 lost their minus sign when numericSeparator was true. Fixed formatNumber function to preserve sign by using original string representation. Also corrected test expectations for scientific notation to not apply numeric separators. Fixes: https://github.com/nodejs/node/issues/59376 --- lib/internal/util/inspect.js | 28 +++++++++++++++++++--------- test/parallel/test-util-inspect.js | 21 +++++++++++++++++++++ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index 78318243e65eca..f0e7b067d14252 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -1684,23 +1684,33 @@ function formatNumber(fn, number, numericSeparator) { } return fn(`${number}`, 'number'); } + + const numberString = String(number); const integer = MathTrunc(number); - const string = String(integer); + if (integer === number) { - if (!NumberIsFinite(number) || StringPrototypeIncludes(string, 'e')) { - return fn(string, 'number'); + if (!NumberIsFinite(number) || StringPrototypeIncludes(numberString, 'e')) { + return fn(numberString, 'number'); } - return fn(`${addNumericSeparator(string)}`, 'number'); + return fn(`${addNumericSeparator(numberString)}`, 'number'); } if (NumberIsNaN(number)) { - return fn(string, 'number'); + return fn(numberString, 'number'); + } + + // Find decimal point position in original string + const decimalIndex = StringPrototypeIndexOf(numberString, '.'); + if (decimalIndex === -1) { + return fn(numberString, 'number'); } + + const integerPart = StringPrototypeSlice(numberString, 0, decimalIndex); + const fractionalPart = StringPrototypeSlice(numberString, decimalIndex + 1); + return fn(`${ - addNumericSeparator(string) + addNumericSeparator(integerPart) }.${ - addNumericSeparatorEnd( - StringPrototypeSlice(String(number), string.length + 1), - ) + addNumericSeparatorEnd(fractionalPart) }`, 'number'); } diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index b06f6814e4985a..9cc8c20df5c887 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -3324,6 +3324,27 @@ assert.strictEqual( util.inspect(-123456789.12345678, { numericSeparator: true }), '-123_456_789.123_456_78' ); + + // Regression test for https://github.com/nodejs/node/issues/59376 + // numericSeparator should work correctly for negative fractional numbers + { + // Test the exact values from the GitHub issue + const values = [0.1234, -0.12, -0.123, -0.1234, -1.234]; + assert.strictEqual( + util.inspect(values, { numericSeparator: true }), + '[ 0.123_4, -0.12, -0.123, -0.123_4, -1.234 ]' + ); + + // Test individual negative fractional numbers between -1 and 0 + assert.strictEqual( + util.inspect(-0.1234, { numericSeparator: true }), + '-0.123_4' + ); + assert.strictEqual( + util.inspect(-0.12345, { numericSeparator: true }), + '-0.123_45' + ); + } } // Regression test for https://github.com/nodejs/node/issues/41244 From 97b7fcbca8c9eb960f15ac1b72b17952e91e9ebd Mon Sep 17 00:00:00 2001 From: sangwook Date: Fri, 8 Aug 2025 00:28:56 +0900 Subject: [PATCH 2/3] util: clarify unreachable decimalIndex === -1 branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an English inline comment explaining that only non-integer numbers reach this section, because integers (including exponential notation and Infinity) are handled earlier. The comment also notes the IEEE-754 ±2^53 exact-integer range and states that the branch is kept only as a defensive fallback. --- lib/internal/util/inspect.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index f0e7b067d14252..56e6dd5e8801b2 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -1692,13 +1692,21 @@ function formatNumber(fn, number, numericSeparator) { if (!NumberIsFinite(number) || StringPrototypeIncludes(numberString, 'e')) { return fn(numberString, 'number'); } - return fn(`${addNumericSeparator(numberString)}`, 'number'); + return fn(addNumericSeparator(numberString), 'number'); } if (NumberIsNaN(number)) { return fn(numberString, 'number'); } - // Find decimal point position in original string + // --------------------------------------------------------------------------- + // From this point on, only *non-integer* numbers will reach the code below. + // Values where `integer === number` (plain integers, exponential notation, + // Infinity, etc.) have already been returned in the previous branch. + // For IEEE-754 doubles within the ±2^53 exact-integer range, `String(number)` + // never inserts a decimal point, therefore encountering + // `decimalIndex === -1` in the next lines should be impossible under + // normal circumstances. (kept here as a defensive fallback) + // --------------------------------------------------------------------------- const decimalIndex = StringPrototypeIndexOf(numberString, '.'); if (decimalIndex === -1) { return fn(numberString, 'number'); From 11ccf3fe10a6b75eed994f12f710426545710732 Mon Sep 17 00:00:00 2001 From: sangwook <73056306+Han5991@users.noreply.github.com> Date: Thu, 4 Sep 2025 21:22:19 +0900 Subject: [PATCH 3/3] util: remove unreachable code in formatNumber Remove unreachable decimalIndex === -1 defensive branch. Co-authored-by: Ruben Bridgewater --- lib/internal/util/inspect.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index 56e6dd5e8801b2..6e0799153b3d40 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -1698,20 +1698,7 @@ function formatNumber(fn, number, numericSeparator) { return fn(numberString, 'number'); } - // --------------------------------------------------------------------------- - // From this point on, only *non-integer* numbers will reach the code below. - // Values where `integer === number` (plain integers, exponential notation, - // Infinity, etc.) have already been returned in the previous branch. - // For IEEE-754 doubles within the ±2^53 exact-integer range, `String(number)` - // never inserts a decimal point, therefore encountering - // `decimalIndex === -1` in the next lines should be impossible under - // normal circumstances. (kept here as a defensive fallback) - // --------------------------------------------------------------------------- const decimalIndex = StringPrototypeIndexOf(numberString, '.'); - if (decimalIndex === -1) { - return fn(numberString, 'number'); - } - const integerPart = StringPrototypeSlice(numberString, 0, decimalIndex); const fractionalPart = StringPrototypeSlice(numberString, decimalIndex + 1);