mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-04 13:30:31 +00:00
LibJS: Support large number of decimals in Number.prototype.toFixed
The spec asks us to perform some calculations that quickly exceed an `u64`, but instead of jumping through hoops we can rely on our AK implementation of floating point formatting to come up with the correctly rounded result. Note that most other JS engines seem to diverge from the spec as well and fall back to a generic dtoa path.
This commit is contained in:
parent
b015926f8e
commit
c58193bafa
Notes:
sideshowbarker
2024-07-17 02:35:27 +09:00
Author: https://github.com/gmta Commit: https://github.com/SerenityOS/serenity/commit/c58193bafa Pull-request: https://github.com/SerenityOS/serenity/pull/21454
2 changed files with 35 additions and 34 deletions
|
@ -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<size_t>(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
|
||||
|
|
|
@ -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 => {
|
||||
|
|
Loading…
Reference in a new issue