mirror of
https://github.com/RGBCube/serenity
synced 2025-07-28 05:37:44 +00:00
LibJS: Implement Intl.NumberFormat V3's [[UseGrouping]] changes
In the main spec, [[UseGrouping]] can be true or false. In V3, it may be one of: auto: Respect the per-locale preference for grouping. always: Ignore per-locale preference for grouping and always insert the grouping separator (note: true is now an alias for always). min2: Ignore per-locale preference for grouping and only insert the grouping separator if the primary group has at least 2 digits. false: Ignore per-locale preference for grouping and never insert the grouping separator.
This commit is contained in:
parent
cd4ee46b70
commit
733192089f
3 changed files with 142 additions and 45 deletions
|
@ -428,13 +428,6 @@ static ALWAYS_INLINE bool is_greater_than(Value number, i64 rhs)
|
|||
return number.as_bigint().big_integer() > Crypto::SignedBigInteger::create_from(rhs);
|
||||
}
|
||||
|
||||
static ALWAYS_INLINE bool is_greater_than_or_equal(Value number, i64 rhs)
|
||||
{
|
||||
if (number.is_number())
|
||||
return number.as_double() >= rhs;
|
||||
return number.as_bigint().big_integer() >= Crypto::SignedBigInteger::create_from(rhs);
|
||||
}
|
||||
|
||||
static ALWAYS_INLINE bool is_less_than(Value number, i64 rhs)
|
||||
{
|
||||
if (number.is_number())
|
||||
|
@ -682,15 +675,31 @@ Vector<PatternPartition> partition_number_pattern(GlobalObject& global_object, N
|
|||
return result;
|
||||
}
|
||||
|
||||
static Vector<StringView> separate_integer_into_groups(Unicode::NumberGroupings const& grouping_sizes, StringView integer)
|
||||
static Vector<StringView> separate_integer_into_groups(Unicode::NumberGroupings const& grouping_sizes, StringView integer, NumberFormat::UseGrouping use_grouping)
|
||||
{
|
||||
Utf8View utf8_integer { integer };
|
||||
if (utf8_integer.length() <= grouping_sizes.primary_grouping_size)
|
||||
return { integer };
|
||||
|
||||
size_t index = utf8_integer.length() - grouping_sizes.primary_grouping_size;
|
||||
if (index < grouping_sizes.minimum_grouping_digits)
|
||||
return { integer };
|
||||
|
||||
switch (use_grouping) {
|
||||
case NumberFormat::UseGrouping::Min2:
|
||||
if (utf8_integer.length() < 5)
|
||||
return { integer };
|
||||
break;
|
||||
|
||||
case NumberFormat::UseGrouping::Auto:
|
||||
if (index < grouping_sizes.minimum_grouping_digits)
|
||||
return { integer };
|
||||
break;
|
||||
|
||||
case NumberFormat::UseGrouping::Always:
|
||||
break;
|
||||
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
Vector<StringView> groups;
|
||||
|
||||
|
@ -712,6 +721,7 @@ static Vector<StringView> separate_integer_into_groups(Unicode::NumberGroupings
|
|||
}
|
||||
|
||||
// 15.5.5 PartitionNotationSubPattern ( numberFormat, x, n, exponent ), https://tc39.es/ecma402/#sec-partitionnotationsubpattern
|
||||
// 1.1.7 PartitionNotationSubPattern ( numberFormat, x, n, exponent ), https://tc39.es/proposal-intl-numberformat-v3/out/numberformat/proposed.html#sec-partitionnotationsubpattern
|
||||
Vector<PatternPartition> partition_notation_sub_pattern(GlobalObject& global_object, NumberFormat& number_format, Value number, String formatted_string, int exponent)
|
||||
{
|
||||
// 1. Let result be a new empty List.
|
||||
|
@ -779,30 +789,18 @@ Vector<PatternPartition> partition_notation_sub_pattern(GlobalObject& global_obj
|
|||
// b. Let fraction be undefined.
|
||||
}
|
||||
|
||||
// FIXME: Handle all NumberFormat V3 [[UseGrouping]] options.
|
||||
bool use_grouping = number_format.use_grouping() != NumberFormat::UseGrouping::False;
|
||||
|
||||
// FIXME: The spec doesn't indicate this, but grouping should be disabled for numbers less than 10,000 when the notation is compact.
|
||||
// This is addressed in Intl.NumberFormat V3 with the "min2" [[UseGrouping]] option. However, test262 explicitly expects this
|
||||
// behavior in the "de-DE" locale tests, because this is how ICU (and therefore V8, SpiderMoney, etc.) has always behaved.
|
||||
//
|
||||
// So, in locales "de-*", we must have:
|
||||
// Intl.NumberFormat("de", {notation: "compact"}).format(1234) === "1234"
|
||||
// Intl.NumberFormat("de", {notation: "compact"}).format(12345) === "12.345"
|
||||
// Intl.NumberFormat("de").format(1234) === "1.234"
|
||||
// Intl.NumberFormat("de").format(12345) === "12.345"
|
||||
//
|
||||
// See: https://github.com/tc39/proposal-intl-numberformat-v3/issues/3
|
||||
if (number_format.has_compact_format())
|
||||
use_grouping = is_greater_than_or_equal(number, 10'000);
|
||||
|
||||
// 6. If the numberFormat.[[UseGrouping]] is true, then
|
||||
if (use_grouping) {
|
||||
// 6. If the numberFormat.[[UseGrouping]] is false, then
|
||||
if (number_format.use_grouping() == NumberFormat::UseGrouping::False) {
|
||||
// a. Append a new Record { [[Type]]: "integer", [[Value]]: integer } as the last element of result.
|
||||
result.append({ "integer"sv, integer });
|
||||
}
|
||||
// 7. Else,
|
||||
else {
|
||||
// a. Let groupSepSymbol be the implementation-, locale-, and numbering system-dependent (ILND) String representing the grouping separator.
|
||||
auto group_sep_symbol = Unicode::get_number_system_symbol(number_format.data_locale(), number_format.numbering_system(), Unicode::NumericSymbol::Group).value_or(","sv);
|
||||
|
||||
// b. Let groups be a List whose elements are, in left to right order, the substrings defined by ILND set of locations within the integer.
|
||||
auto groups = separate_integer_into_groups(*grouping_sizes, integer);
|
||||
// b. Let groups be a List whose elements are, in left to right order, the substrings defined by ILND set of locations within the integer, which may depend on the value of numberFormat.[[UseGrouping]].
|
||||
auto groups = separate_integer_into_groups(*grouping_sizes, integer, number_format.use_grouping());
|
||||
|
||||
// c. Assert: The number of elements in groups List is greater than 0.
|
||||
VERIFY(!groups.is_empty());
|
||||
|
@ -822,11 +820,6 @@ Vector<PatternPartition> partition_notation_sub_pattern(GlobalObject& global_obj
|
|||
}
|
||||
}
|
||||
}
|
||||
// 7. Else,
|
||||
else {
|
||||
// a. Append a new Record { [[Type]]: "integer", [[Value]]: integer } as the last element of result.
|
||||
result.append({ "integer"sv, integer });
|
||||
}
|
||||
|
||||
// 8. If fraction is not undefined, then
|
||||
if (fraction.has_value()) {
|
||||
|
|
|
@ -347,29 +347,89 @@ describe("style=decimal", () => {
|
|||
expect(ar.format(-1)).toBe("\u061c-\u0661");
|
||||
});
|
||||
|
||||
test("useGrouping=true", () => {
|
||||
const en = new Intl.NumberFormat("en", { useGrouping: true });
|
||||
test("useGrouping=always", () => {
|
||||
const en = new Intl.NumberFormat("en", { useGrouping: "always" });
|
||||
expect(en.format(123)).toBe("123");
|
||||
expect(en.format(1234)).toBe("1,234");
|
||||
expect(en.format(12345)).toBe("12,345");
|
||||
expect(en.format(123456)).toBe("123,456");
|
||||
expect(en.format(1234567)).toBe("1,234,567");
|
||||
|
||||
const enIn = new Intl.NumberFormat("en-IN", { useGrouping: true });
|
||||
const enIn = new Intl.NumberFormat("en-IN", { useGrouping: "always" });
|
||||
expect(enIn.format(123)).toBe("123");
|
||||
expect(enIn.format(1234)).toBe("1,234");
|
||||
expect(enIn.format(12345)).toBe("12,345");
|
||||
expect(enIn.format(123456)).toBe("1,23,456");
|
||||
expect(enIn.format(1234567)).toBe("12,34,567");
|
||||
|
||||
const ar = new Intl.NumberFormat("ar", { useGrouping: true });
|
||||
const ar = new Intl.NumberFormat("ar", { useGrouping: "always" });
|
||||
expect(ar.format(123)).toBe("\u0661\u0662\u0663");
|
||||
expect(ar.format(1234)).toBe("\u0661\u066c\u0662\u0663\u0664");
|
||||
expect(ar.format(12345)).toBe("\u0661\u0662\u066c\u0663\u0664\u0665");
|
||||
expect(ar.format(123456)).toBe("\u0661\u0662\u0663\u066c\u0664\u0665\u0666");
|
||||
expect(ar.format(1234567)).toBe("\u0661\u066c\u0662\u0663\u0664\u066c\u0665\u0666\u0667");
|
||||
|
||||
const plPl = new Intl.NumberFormat("pl-PL", { useGrouping: true });
|
||||
const plPl = new Intl.NumberFormat("pl-PL", { useGrouping: "always" });
|
||||
expect(plPl.format(123)).toBe("123");
|
||||
expect(plPl.format(1234)).toBe("1\u00a0234");
|
||||
expect(plPl.format(12345)).toBe("12\u00a0345");
|
||||
expect(plPl.format(123456)).toBe("123\u00a0456");
|
||||
expect(plPl.format(1234567)).toBe("1\u00a0234\u00a0567");
|
||||
});
|
||||
|
||||
test("useGrouping=auto", () => {
|
||||
const en = new Intl.NumberFormat("en", { useGrouping: "auto" });
|
||||
expect(en.format(123)).toBe("123");
|
||||
expect(en.format(1234)).toBe("1,234");
|
||||
expect(en.format(12345)).toBe("12,345");
|
||||
expect(en.format(123456)).toBe("123,456");
|
||||
expect(en.format(1234567)).toBe("1,234,567");
|
||||
|
||||
const enIn = new Intl.NumberFormat("en-IN", { useGrouping: "auto" });
|
||||
expect(enIn.format(123)).toBe("123");
|
||||
expect(enIn.format(1234)).toBe("1,234");
|
||||
expect(enIn.format(12345)).toBe("12,345");
|
||||
expect(enIn.format(123456)).toBe("1,23,456");
|
||||
expect(enIn.format(1234567)).toBe("12,34,567");
|
||||
|
||||
const ar = new Intl.NumberFormat("ar", { useGrouping: "auto" });
|
||||
expect(ar.format(123)).toBe("\u0661\u0662\u0663");
|
||||
expect(ar.format(1234)).toBe("\u0661\u066c\u0662\u0663\u0664");
|
||||
expect(ar.format(12345)).toBe("\u0661\u0662\u066c\u0663\u0664\u0665");
|
||||
expect(ar.format(123456)).toBe("\u0661\u0662\u0663\u066c\u0664\u0665\u0666");
|
||||
expect(ar.format(1234567)).toBe("\u0661\u066c\u0662\u0663\u0664\u066c\u0665\u0666\u0667");
|
||||
|
||||
const plPl = new Intl.NumberFormat("pl-PL", { useGrouping: "auto" });
|
||||
expect(plPl.format(123)).toBe("123");
|
||||
expect(plPl.format(1234)).toBe("1234");
|
||||
expect(plPl.format(12345)).toBe("12\u00a0345");
|
||||
expect(plPl.format(123456)).toBe("123\u00a0456");
|
||||
expect(plPl.format(1234567)).toBe("1\u00a0234\u00a0567");
|
||||
});
|
||||
|
||||
test("useGrouping=min2", () => {
|
||||
const en = new Intl.NumberFormat("en", { useGrouping: "min2" });
|
||||
expect(en.format(123)).toBe("123");
|
||||
expect(en.format(1234)).toBe("1234");
|
||||
expect(en.format(12345)).toBe("12,345");
|
||||
expect(en.format(123456)).toBe("123,456");
|
||||
expect(en.format(1234567)).toBe("1,234,567");
|
||||
|
||||
const enIn = new Intl.NumberFormat("en-IN", { useGrouping: "min2" });
|
||||
expect(enIn.format(123)).toBe("123");
|
||||
expect(enIn.format(1234)).toBe("1234");
|
||||
expect(enIn.format(12345)).toBe("12,345");
|
||||
expect(enIn.format(123456)).toBe("1,23,456");
|
||||
expect(enIn.format(1234567)).toBe("12,34,567");
|
||||
|
||||
const ar = new Intl.NumberFormat("ar", { useGrouping: "min2" });
|
||||
expect(ar.format(123)).toBe("\u0661\u0662\u0663");
|
||||
expect(ar.format(1234)).toBe("\u0661\u0662\u0663\u0664");
|
||||
expect(ar.format(12345)).toBe("\u0661\u0662\u066c\u0663\u0664\u0665");
|
||||
expect(ar.format(123456)).toBe("\u0661\u0662\u0663\u066c\u0664\u0665\u0666");
|
||||
expect(ar.format(1234567)).toBe("\u0661\u066c\u0662\u0663\u0664\u066c\u0665\u0666\u0667");
|
||||
|
||||
const plPl = new Intl.NumberFormat("pl-PL", { useGrouping: "min2" });
|
||||
expect(plPl.format(123)).toBe("123");
|
||||
expect(plPl.format(1234)).toBe("1234");
|
||||
expect(plPl.format(12345)).toBe("12\u00a0345");
|
||||
|
|
|
@ -171,8 +171,34 @@ describe("style=decimal", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
test("useGrouping=true", () => {
|
||||
const en = new Intl.NumberFormat("en", { useGrouping: true });
|
||||
test("useGrouping=always", () => {
|
||||
const en = new Intl.NumberFormat("en", { useGrouping: "always" });
|
||||
expect(en.formatToParts(1234)).toEqual([
|
||||
{ type: "integer", value: "1" },
|
||||
{ type: "group", value: "," },
|
||||
{ type: "integer", value: "234" },
|
||||
]);
|
||||
expect(en.formatToParts(12345)).toEqual([
|
||||
{ type: "integer", value: "12" },
|
||||
{ type: "group", value: "," },
|
||||
{ type: "integer", value: "345" },
|
||||
]);
|
||||
|
||||
const plPl = new Intl.NumberFormat("pl-PL", { useGrouping: "always" });
|
||||
expect(plPl.formatToParts(1234)).toEqual([
|
||||
{ type: "integer", value: "1" },
|
||||
{ type: "group", value: "\u00a0" },
|
||||
{ type: "integer", value: "234" },
|
||||
]);
|
||||
expect(plPl.formatToParts(12345)).toEqual([
|
||||
{ type: "integer", value: "12" },
|
||||
{ type: "group", value: "\u00a0" },
|
||||
{ type: "integer", value: "345" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("useGrouping=auto", () => {
|
||||
const en = new Intl.NumberFormat("en", { useGrouping: "auto" });
|
||||
expect(en.formatToParts(123456)).toEqual([
|
||||
{ type: "integer", value: "123" },
|
||||
{ type: "group", value: "," },
|
||||
|
@ -186,7 +212,7 @@ describe("style=decimal", () => {
|
|||
{ type: "integer", value: "567" },
|
||||
]);
|
||||
|
||||
const enIn = new Intl.NumberFormat("en-IN", { useGrouping: true });
|
||||
const enIn = new Intl.NumberFormat("en-IN", { useGrouping: "auto" });
|
||||
expect(enIn.formatToParts(123456)).toEqual([
|
||||
{ type: "integer", value: "1" },
|
||||
{ type: "group", value: "," },
|
||||
|
@ -202,7 +228,7 @@ describe("style=decimal", () => {
|
|||
{ type: "integer", value: "567" },
|
||||
]);
|
||||
|
||||
const ar = new Intl.NumberFormat("ar", { useGrouping: true });
|
||||
const ar = new Intl.NumberFormat("ar", { useGrouping: "auto" });
|
||||
expect(ar.formatToParts(123456)).toEqual([
|
||||
{ type: "integer", value: "\u0661\u0662\u0663" },
|
||||
{ type: "group", value: "\u066c" },
|
||||
|
@ -217,6 +243,24 @@ describe("style=decimal", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
test("useGrouping=min2", () => {
|
||||
const en = new Intl.NumberFormat("en", { useGrouping: "min2" });
|
||||
expect(en.formatToParts(1234)).toEqual([{ type: "integer", value: "1234" }]);
|
||||
expect(en.formatToParts(12345)).toEqual([
|
||||
{ type: "integer", value: "12" },
|
||||
{ type: "group", value: "," },
|
||||
{ type: "integer", value: "345" },
|
||||
]);
|
||||
|
||||
const plPl = new Intl.NumberFormat("pl-PL", { useGrouping: "min2" });
|
||||
expect(plPl.formatToParts(1234)).toEqual([{ type: "integer", value: "1234" }]);
|
||||
expect(plPl.formatToParts(12345)).toEqual([
|
||||
{ type: "integer", value: "12" },
|
||||
{ type: "group", value: "\u00a0" },
|
||||
{ type: "integer", value: "345" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("useGrouping=false", () => {
|
||||
const en = new Intl.NumberFormat("en", { useGrouping: false });
|
||||
expect(en.formatToParts(123456)).toEqual([{ type: "integer", value: "123456" }]);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue