From 37ab7cc694df2b1608945a9f8b04c6776cd58d04 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sat, 16 Jul 2022 12:46:14 -0400 Subject: [PATCH] LibJS: Implement Intl.NumberFormat V3's [[TrailingZeroDisplay]] changes --- .../LibJS/Runtime/Intl/NumberFormat.cpp | 29 +++++++++--- .../NumberFormat.prototype.format.js | 44 +++++++++++++++++++ 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp index 53d757f34a..bcb69eed40 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -449,6 +450,15 @@ static ALWAYS_INLINE bool is_zero(Value number) return number.as_bigint().big_integer().is_zero(); } +static bool modulo_is_zero(Value lhs, i8 rhs) +{ + if (lhs.is_number()) + return modulo(lhs.as_double(), rhs) == 0; + + auto rhs_bigint = Crypto::SignedBigInteger::create_from(rhs); + return modulo(lhs.as_bigint().big_integer(), rhs_bigint).is_zero(); +} + static ALWAYS_INLINE bool is_greater_than(Value number, i64 rhs) { if (number.is_number()) @@ -560,13 +570,22 @@ FormatResult format_numeric_to_string(GlobalObject& global_object, NumberFormatB // 7. Let string be result.[[FormattedString]]. auto string = move(result.formatted_string); - // 8. Let int be result.[[IntegerDigitsCount]]. + // 8. If intlObject.[[TrailingZeroDisplay]] is "stripIfInteger" and x modulo 1 = 0, then + if ((intl_object.trailing_zero_display() == NumberFormat::TrailingZeroDisplay::StripIfInteger) && modulo_is_zero(number, 1)) { + // a. If string contains ".", then + if (auto index = string.find('.'); index.has_value()) { + // i. Set string to the substring of string from index 0 to the index of ".". + string = string.substring(0, *index); + } + } + + // 9. Let int be result.[[IntegerDigitsCount]]. int digits = result.digits; - // 9. Let minInteger be intlObject.[[MinimumIntegerDigits]]. + // 10. Let minInteger be intlObject.[[MinimumIntegerDigits]]. int min_integer = intl_object.min_integer_digits(); - // 10. If int < minInteger, then + // 11. If int < minInteger, then if (digits < min_integer) { // a. Let forwardZeros be the String consisting of minInteger–int occurrences of the character "0". auto forward_zeros = String::repeated('0', min_integer - digits); @@ -575,13 +594,13 @@ FormatResult format_numeric_to_string(GlobalObject& global_object, NumberFormatB string = String::formatted("{}{}", forward_zeros, string); } - // 11. If isNegative, then + // 12. If isNegative, then if (is_negative) { // a. Let x be -x. number = multiply(global_object, number, -1); } - // 12. Return the Record { [[RoundedNumber]]: x, [[FormattedString]]: string }. + // 13. Return the Record { [[RoundedNumber]]: x, [[FormattedString]]: string }. return { move(string), number }; } diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.format.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.format.js index 5e9a135e0a..508636af02 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.format.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.format.js @@ -567,6 +567,50 @@ describe("style=decimal", () => { expect(nf("ar", undefined, 2, undefined, 2).format(1.23)).toBe("\u0661\u066b\u0662\u0663"); expect(nf("ar", undefined, 3, undefined, 1).format(1.23)).toBe("\u0661\u066b\u0662\u0663"); }); + + test("trailingZeroDisplay=auto", () => { + const en = new Intl.NumberFormat("en", { + trailingZeroDisplay: "auto", + minimumSignificantDigits: 5, + }); + expect(en.format(1)).toBe("1.0000"); + expect(en.format(1n)).toBe("1.0000"); + expect(en.format(12)).toBe("12.000"); + expect(en.format(12n)).toBe("12.000"); + expect(en.format(1.2)).toBe("1.2000"); + + const ar = new Intl.NumberFormat("ar", { + trailingZeroDisplay: "auto", + minimumSignificantDigits: 5, + }); + expect(ar.format(1)).toBe("\u0661\u066b\u0660\u0660\u0660\u0660"); + expect(ar.format(1n)).toBe("\u0661\u066b\u0660\u0660\u0660\u0660"); + expect(ar.format(12)).toBe("\u0661\u0662\u066b\u0660\u0660\u0660"); + expect(ar.format(12n)).toBe("\u0661\u0662\u066b\u0660\u0660\u0660"); + expect(ar.format(1.2)).toBe("\u0661\u066b\u0662\u0660\u0660\u0660"); + }); + + test("trailingZeroDisplay=stripIfInteger", () => { + const en = new Intl.NumberFormat("en", { + trailingZeroDisplay: "stripIfInteger", + minimumSignificantDigits: 5, + }); + expect(en.format(1)).toBe("1"); + expect(en.format(1n)).toBe("1"); + expect(en.format(12)).toBe("12"); + expect(en.format(12n)).toBe("12"); + expect(en.format(1.2)).toBe("1.2000"); + + const ar = new Intl.NumberFormat("ar", { + trailingZeroDisplay: "stripIfInteger", + minimumSignificantDigits: 5, + }); + expect(ar.format(1)).toBe("\u0661"); + expect(ar.format(1n)).toBe("\u0661"); + expect(ar.format(12)).toBe("\u0661\u0662"); + expect(ar.format(12n)).toBe("\u0661\u0662"); + expect(ar.format(1.2)).toBe("\u0661\u066b\u0662\u0660\u0660\u0660"); + }); }); describe("style=percent", () => {