From a9c9c8c07625f3432d761be8d604d888bf95890d Mon Sep 17 00:00:00 2001 From: Ben Abraham Date: Wed, 2 Mar 2022 00:15:00 -0500 Subject: [PATCH] LibJS: Fix rounding issues in Number.toFixed toFixed was not rounding properly when trimming a number. ie: (0.00006).toFixed(4) should be "0.00001" but was returning "0.0000" --- .../LibJS/Runtime/NumberPrototype.cpp | 38 ++++++++++++++++++- .../Number/Number.prototype.toFixed.js | 1 + 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp b/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp index c7f75a8b79..df6fce6153 100644 --- a/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp @@ -269,16 +269,50 @@ JS_DEFINE_NATIVE_FUNCTION(NumberPrototype::to_fixed) // 8. Let s be the empty String. // 9. If x < 0, then // a. Set s to "-". + auto s = (number < 0 ? "-" : ""); // b. Set x to -x. + if (number < 0) + number = -number; // 10. If x ≥ 10^21, then if (fabs(number) >= 1e+21) return js_string(vm, MUST(number_value.to_string(global_object))); // 11. Else, - // a-c. NOTE: The number to string formatting algorithm is handled by String::formatted(). + // 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" : String::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 = String::repeated('0', fraction_digits + 1 - k); + + // 2. Set m to the string-concatenation of z and m. + m = String::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 = String::formatted("{}.{}", + m.substring_view(0, k - fraction_digits), + m.substring_view(k - fraction_digits, fraction_digits)); + } + // 12. Return the string-concatenation of s and m. - return js_string(vm, String::formatted("{:0.{1}}", number, static_cast(fraction_digits))); + return js_string(vm, String::formatted("{}{}", s, m)); } // 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 6d0f646140..aec3648732 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toFixed.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toFixed.js @@ -12,6 +12,7 @@ describe("correct behavior", () => { [12.81646112, 3, "12.816"], [84.23, 4, "84.2300"], [3.00003, 5, "3.00003"], + [0.00006, 4, "0.0001"], // Numbers >= 1e+21 [1e21, 5, "1e+21"], [1e22, 0, "1e+22"],