diff --git a/src/js/internal/util/inspect.js b/src/js/internal/util/inspect.js index 09d55ecafcbe..414189d9b2ac 100644 --- a/src/js/internal/util/inspect.js +++ b/src/js/internal/util/inspect.js @@ -2205,28 +2205,33 @@ function addNumericSeparatorEnd(integerString) { const remainingText = remaining => `... ${remaining} more item${remaining > 1 ? "s" : ""}`; function formatNumber(fn, number, numericSeparator) { + // Format -0 as '-0'. Checking `number === -0` won't distinguish 0 from -0. + // String(-0) === '0', so this must be checked before any String() conversion. + if (ObjectIs(number, -0)) { + return fn("-0", "number"); + } if (!numericSeparator) { - // Format -0 as '-0'. Checking `number === -0` won't distinguish 0 from -0. - if (ObjectIs(number, -0)) { - return fn("-0", "number"); - } 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"); + if (NumberIsNaN(number) || StringPrototypeIncludes(numberString, "e")) { + return fn(numberString, "number"); } - return fn( - `${addNumericSeparator(string)}.${addNumericSeparatorEnd(StringPrototypeSlice(String(number), string.length + 1))}`, - "number", - ); + + const decimalIndex = StringPrototypeIndexOf(numberString, "."); + const integerPart = StringPrototypeSlice(numberString, 0, decimalIndex); + const fractionalPart = StringPrototypeSlice(numberString, decimalIndex + 1); + + return fn(`${addNumericSeparator(integerPart)}.${addNumericSeparatorEnd(fractionalPart)}`, "number"); } function formatBigInt(fn, bigint, numericSeparator) { diff --git a/test/js/node/util/util.test.js b/test/js/node/util/util.test.js index 7689c1230321..47e816bb8a36 100644 --- a/test/js/node/util/util.test.js +++ b/test/js/node/util/util.test.js @@ -462,6 +462,49 @@ describe("util", () => { }); }); + describe("inspect numericSeparator", () => { + const sep = v => util.inspect(v, { numericSeparator: true }); + + it("formats negative fractional numbers (#23098)", () => { + expect(sep([0.1234, -0.12, -0.123, -0.1234, -1.234])).toBe("[ 0.123_4, -0.12, -0.123, -0.123_4, -1.234 ]"); + }); + + it("matches Node for each negative fraction", () => { + expect(sep(-0.12)).toBe("-0.12"); + expect(sep(-0.123)).toBe("-0.123"); + expect(sep(-0.1234)).toBe("-0.123_4"); + expect(sep(-1.234)).toBe("-1.234"); + expect(sep(-12345.6789)).toBe("-12_345.678_9"); + }); + + it("keeps the sign on -0", () => { + expect(sep(-0)).toBe("-0"); + expect(sep(0)).toBe("0"); + }); + + it("groups large integers and fractions", () => { + expect(sep(1234567)).toBe("1_234_567"); + expect(sep(-1234567)).toBe("-1_234_567"); + expect(sep(123456.789)).toBe("123_456.789"); + expect(sep(0.1234)).toBe("0.123_4"); + }); + + it("leaves exponent-form numbers intact", () => { + expect(sep(1e-7)).toBe("1e-7"); + expect(sep(-1e-7)).toBe("-1e-7"); + expect(sep(2e-7)).toBe("2e-7"); + expect(sep(1.23456e-7)).toBe("1.23456e-7"); + expect(sep(-0.5)).toBe("-0.5"); + expect(sep(1234567891234567891234)).toBe("1.234567891234568e+21"); + }); + + it("handles non-finite values", () => { + expect(sep(NaN)).toBe("NaN"); + expect(sep(Infinity)).toBe("Infinity"); + expect(sep(-Infinity)).toBe("-Infinity"); + }); + }); + describe("getSystemErrorName", () => { for (const item of ["test", {}, []]) { it(`throws when passing: ${item}`, () => {