diff --git a/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp b/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp index 0fae98878d..75628bebda 100644 --- a/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp @@ -232,44 +232,30 @@ JS_DEFINE_NATIVE_FUNCTION(NumberPrototype::to_fixed) number = -number; // 10. If x ≥ 10^21, then - if (fabs(number) >= 1e+21) + // a. Let m be ! ToString(𝔽(x)). + if (number >= 1e+21) return PrimitiveString::create(vm, MUST(number_value.to_deprecated_string(vm))); // 11. Else, - // a. Let n be an integer for which n / (10^f) - x is as close to zero as possible. If there are two such n, pick the larger n. - // FIXME: This breaks down with values of `fraction_digits` > 23 - auto n = round(pow(10.0f, fraction_digits) * number); - - // b. If n = 0, let m be the String "0". Otherwise, let m be the String value consisting of the digits of the decimal representation of n (in order, with no leading zeroes). - auto m = (n == 0 ? "0" : DeprecatedString::formatted("{}", n)); - - // c. If f ≠ 0, then - if (fraction_digits != 0) { - // i. Let k be the length of m. - auto k = static_cast(m.length()); - - // ii. If k ≤ f, then - if (k <= fraction_digits) { - // 1. Let z be the String value consisting of f + 1 - k occurrences of the code unit 0x0030 (DIGIT ZERO). - auto z = DeprecatedString::repeated('0', fraction_digits + 1 - k); - - // 2. Set m to the string-concatenation of z and m. - m = DeprecatedString::formatted("{}{}", z, m); - - // 3. Set k to f + 1. - k = fraction_digits + 1; - } - - // iii. Let a be the first k - f code units of m. - // iv. Let b be the other f code units of m. - // v. Set m to the string-concatenation of a, ".", and b. - m = DeprecatedString::formatted("{}.{}", - m.substring_view(0, k - fraction_digits), - m.substring_view(k - fraction_digits, fraction_digits)); - } - + // a. Let n be an integer for which n / (10^f) - x is as close to zero as possible. If there are two such n, pick the larger n. + // b. If n = 0, let m be the String "0". Otherwise, let m be the String value consisting of the digits of the decimal representation of n (in order, with no leading zeroes). + // c. If f ≠ 0, then + // i. Let k be the length of m. + // ii. If k ≤ f, then + // 1. Let z be the String value consisting of f + 1 - k occurrences of the code unit 0x0030 (DIGIT ZERO). + // 2. Set m to the string-concatenation of z and m. + // 3. Set k to f + 1. + // iii. Let a be the first k - f code units of m. + // iv. Let b be the other f code units of m. + // v. Set m to the string-concatenation of a, ".", and b. // 12. Return the string-concatenation of s and m. - return PrimitiveString::create(vm, DeprecatedString::formatted("{}{}", s, m)); + + // NOTE: the above steps are effectively trying to create a formatted string of the + // `number` double. Instead of generating a huge, unwieldy `n`, we format + // the double using our existing formatting code. + + auto number_format_string = DeprecatedString::formatted("{{}}{{:.{}f}}", fraction_digits); + return PrimitiveString::create(vm, DeprecatedString::formatted(number_format_string, s, number)); } // 19.2.1 Number.prototype.toLocaleString ( [ locales [ , options ] ] ), https://tc39.es/ecma402/#sup-number.prototype.tolocalestring diff --git a/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toFixed.js b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toFixed.js index aec3648732..3f7e79436d 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toFixed.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toFixed.js @@ -31,6 +31,21 @@ describe("correct behavior", () => { }); }); +describe("large number of digits", () => { + test("maximum", () => { + expect((1).toFixed(100)).toBe( + "1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ); + expect((-3).toFixed(100)).toBe( + "-3.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ); + }); + + test("fractional values", () => { + expect((1.5).toFixed(30)).toBe("1.500000000000000000000000000000"); + }); +}); + describe("errors", () => { test("must be called with numeric |this|", () => { [true, [], {}, Symbol("foo"), "bar", 1n].forEach(value => {