mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 07:30:19 +00:00
LibJS: Implement engineering and scientific number formatting
This commit is contained in:
parent
30fbb7d9cd
commit
4d79ab6866
Notes:
sideshowbarker
2024-07-18 01:08:52 +09:00
Author: https://github.com/trflynn89 Commit: https://github.com/SerenityOS/serenity/commit/4d79ab68660 Pull-request: https://github.com/SerenityOS/serenity/pull/10921 Reviewed-by: https://github.com/linusg ✅
4 changed files with 167 additions and 18 deletions
|
@ -859,9 +859,11 @@ Vector<PatternPartition> partition_notation_sub_pattern(NumberFormat& number_for
|
|||
else {
|
||||
// a. Let notationSubPattern be GetNotationSubPattern(numberFormat, exponent).
|
||||
auto notation_sub_pattern = get_notation_sub_pattern(number_format, exponent);
|
||||
if (!notation_sub_pattern.has_value())
|
||||
return {};
|
||||
|
||||
// b. Let patternParts be PartitionPattern(notationSubPattern).
|
||||
auto pattern_parts = partition_pattern(notation_sub_pattern);
|
||||
auto pattern_parts = partition_pattern(*notation_sub_pattern);
|
||||
|
||||
// c. For each Record { [[Type]], [[Value]] } patternPart of patternParts, do
|
||||
for (auto& pattern_part : pattern_parts) {
|
||||
|
@ -960,20 +962,34 @@ Vector<PatternPartition> partition_notation_sub_pattern(NumberFormat& number_for
|
|||
// vi. Else if p is equal to "scientificSeparator", then
|
||||
else if (part == "scientificSeparator"sv) {
|
||||
// 1. Let scientificSeparator be the ILND String representing the exponent separator.
|
||||
auto scientific_separator = Unicode::get_number_system_symbol(number_format.data_locale(), number_format.numbering_system(), "exponential"sv).value_or("E"sv);
|
||||
// 2. Append a new Record { [[Type]]: "exponentSeparator", [[Value]]: scientificSeparator } as the last element of result.
|
||||
|
||||
// FIXME: Implement this when GetNotationSubPattern is fully implemented.
|
||||
result.append({ "exponentSeparator"sv, scientific_separator });
|
||||
}
|
||||
// vii. Else if p is equal to "scientificExponent", then
|
||||
else if (part == "scientificExponent"sv) {
|
||||
// 1. If exponent < 0, then
|
||||
// a. Let minusSignSymbol be the ILND String representing the minus sign.
|
||||
// b. Append a new Record { [[Type]]: "exponentMinusSign", [[Value]]: minusSignSymbol } as the last element of result.
|
||||
// c. Let exponent be -exponent.
|
||||
// 2. Let exponentResult be ToRawFixed(exponent, 1, 0, 0).
|
||||
// 3. Append a new Record { [[Type]]: "exponentInteger", [[Value]]: exponentResult.[[FormattedString]] } as the last element of result.
|
||||
if (exponent < 0) {
|
||||
// a. Let minusSignSymbol be the ILND String representing the minus sign.
|
||||
auto minus_sign_symbol = Unicode::get_number_system_symbol(number_format.data_locale(), number_format.numbering_system(), "minusSign"sv).value_or("-"sv);
|
||||
|
||||
// FIXME: Implement this when GetNotationSubPattern is fully implemented.
|
||||
// b. Append a new Record { [[Type]]: "exponentMinusSign", [[Value]]: minusSignSymbol } as the last element of result.
|
||||
result.append({ "exponentMinusSign"sv, minus_sign_symbol });
|
||||
|
||||
// c. Let exponent be -exponent.
|
||||
exponent *= -1;
|
||||
}
|
||||
|
||||
// 2. Let exponentResult be ToRawFixed(exponent, 1, 0, 0).
|
||||
// Note: See the implementation of ToRawFixed for why we do not pass the 1.
|
||||
auto exponent_result = to_raw_fixed(exponent, 0, 0);
|
||||
|
||||
// FIXME: The spec does not say to do this, but all of major engines perform this replacement.
|
||||
// Without this, formatting with non-Latin numbering systems will produce non-localized results.
|
||||
exponent_result.formatted_string = replace_digits_for_number_format(number_format, move(exponent_result.formatted_string));
|
||||
|
||||
// 3. Append a new Record { [[Type]]: "exponentInteger", [[Value]]: exponentResult.[[FormattedString]] } as the last element of result.
|
||||
result.append({ "exponentInteger"sv, move(exponent_result.formatted_string) });
|
||||
}
|
||||
// viii. Else,
|
||||
else {
|
||||
|
@ -1454,23 +1470,36 @@ Optional<Variant<StringView, String>> get_number_format_pattern(NumberFormat& nu
|
|||
}
|
||||
|
||||
// 15.1.15 GetNotationSubPattern ( numberFormat, exponent ), https://tc39.es/ecma402/#sec-getnotationsubpattern
|
||||
StringView get_notation_sub_pattern([[maybe_unused]] NumberFormat& number_format, [[maybe_unused]] int exponent)
|
||||
Optional<StringView> get_notation_sub_pattern(NumberFormat& number_format, int exponent)
|
||||
{
|
||||
// FIXME: Implement this.
|
||||
|
||||
// 1. Let localeData be %NumberFormat%.[[LocaleData]].
|
||||
// 2. Let dataLocale be numberFormat.[[DataLocale]].
|
||||
// 3. Let dataLocaleData be localeData.[[<dataLocale>]].
|
||||
// 4. Let notationSubPatterns be dataLocaleData.[[notationSubPatterns]].
|
||||
// 5. Assert: notationSubPatterns is a Record (see 15.3.3).
|
||||
|
||||
// 6. Let notation be numberFormat.[[Notation]].
|
||||
auto notation = number_format.notation();
|
||||
|
||||
// 7. If notation is "scientific" or notation is "engineering", then
|
||||
// a. Return notationSubPatterns.[[scientific]].
|
||||
if ((notation == NumberFormat::Notation::Scientific) || (notation == NumberFormat::Notation::Engineering)) {
|
||||
// a. Return notationSubPatterns.[[scientific]].
|
||||
auto notation_sub_patterns = Unicode::get_standard_number_system_format(number_format.data_locale(), number_format.numbering_system(), Unicode::StandardNumberFormatType::Scientific);
|
||||
if (!notation_sub_patterns.has_value())
|
||||
return {};
|
||||
|
||||
return notation_sub_patterns->zero_format;
|
||||
}
|
||||
// 8. Else if exponent is not 0, then
|
||||
// a. Assert: notation is "compact".
|
||||
// b. Let compactDisplay be numberFormat.[[CompactDisplay]].
|
||||
// c. Let compactPatterns be notationSubPatterns.[[compact]].[[<compactDisplay>]].
|
||||
// d. Return compactPatterns.[[<exponent>]].
|
||||
else if (exponent != 0) {
|
||||
// FIXME: Implement this.
|
||||
|
||||
// a. Assert: notation is "compact".
|
||||
// b. Let compactDisplay be numberFormat.[[CompactDisplay]].
|
||||
// c. Let compactPatterns be notationSubPatterns.[[compact]].[[<compactDisplay>]].
|
||||
// d. Return compactPatterns.[[<exponent>]].
|
||||
}
|
||||
|
||||
// 9. Else,
|
||||
// a. Return "{number}".
|
||||
return "{number}"sv;
|
||||
|
|
|
@ -204,7 +204,7 @@ RawFormatResult to_raw_precision(double number, int min_precision, int max_preci
|
|||
RawFormatResult to_raw_fixed(double number, int min_fraction, int max_fraction);
|
||||
ThrowCompletionOr<void> set_number_format_unit_options(GlobalObject& global_object, NumberFormat& intl_object, Object const& options);
|
||||
Optional<Variant<StringView, String>> get_number_format_pattern(NumberFormat& number_format, double number);
|
||||
StringView get_notation_sub_pattern(NumberFormat& number_format, int exponent);
|
||||
Optional<StringView> get_notation_sub_pattern(NumberFormat& number_format, int exponent);
|
||||
int compute_exponent(NumberFormat& number_format, double number);
|
||||
int compute_exponent_for_magniude(NumberFormat& number_format, int magnitude);
|
||||
|
||||
|
|
|
@ -133,6 +133,58 @@ describe("style=decimal", () => {
|
|||
expect(ar.format(1.234561)).toBe("\u0661\u066b\u0662\u0663\u0664\u0665\u0666");
|
||||
});
|
||||
|
||||
test("notation=scientific", () => {
|
||||
const en = new Intl.NumberFormat("en", { notation: "scientific" });
|
||||
expect(en.format(1)).toBe("1E0");
|
||||
expect(en.format(1.2)).toBe("1.2E0");
|
||||
expect(en.format(12)).toBe("1.2E1");
|
||||
expect(en.format(12.3)).toBe("1.23E1");
|
||||
expect(en.format(123)).toBe("1.23E2");
|
||||
expect(en.format(0.1)).toBe("1E-1");
|
||||
expect(en.format(0.12)).toBe("1.2E-1");
|
||||
expect(en.format(0.01)).toBe("1E-2");
|
||||
|
||||
const ar = new Intl.NumberFormat("ar", { notation: "scientific" });
|
||||
expect(ar.format(1)).toBe("\u0661\u0627\u0633\u0660");
|
||||
expect(ar.format(1.2)).toBe("\u0661\u066b\u0662\u0627\u0633\u0660");
|
||||
expect(ar.format(12)).toBe("\u0661\u066b\u0662\u0627\u0633\u0661");
|
||||
expect(ar.format(12.3)).toBe("\u0661\u066b\u0662\u0663\u0627\u0633\u0661");
|
||||
expect(ar.format(123)).toBe("\u0661\u066b\u0662\u0663\u0627\u0633\u0662");
|
||||
expect(ar.format(0.1)).toBe("\u0661\u0627\u0633\u061c-\u0661");
|
||||
expect(ar.format(0.12)).toBe("\u0661\u066b\u0662\u0627\u0633\u061c-\u0661");
|
||||
expect(ar.format(0.01)).toBe("\u0661\u0627\u0633\u061c-\u0662");
|
||||
});
|
||||
|
||||
test("notation=engineering", () => {
|
||||
const en = new Intl.NumberFormat("en", { notation: "engineering" });
|
||||
expect(en.format(1)).toBe("1E0");
|
||||
expect(en.format(1.2)).toBe("1.2E0");
|
||||
expect(en.format(12)).toBe("12E0");
|
||||
expect(en.format(123)).toBe("123E0");
|
||||
expect(en.format(1234)).toBe("1.234E3");
|
||||
expect(en.format(12345)).toBe("12.345E3");
|
||||
expect(en.format(123456)).toBe("123.456E3");
|
||||
expect(en.format(1234567)).toBe("1.235E6");
|
||||
expect(en.format(0.1)).toBe("100E-3");
|
||||
expect(en.format(0.12)).toBe("120E-3");
|
||||
expect(en.format(1.23)).toBe("1.23E0");
|
||||
|
||||
const ar = new Intl.NumberFormat("ar", { notation: "engineering" });
|
||||
expect(ar.format(1)).toBe("\u0661\u0627\u0633\u0660");
|
||||
expect(ar.format(1.2)).toBe("\u0661\u066b\u0662\u0627\u0633\u0660");
|
||||
expect(ar.format(12)).toBe("\u0661\u0662\u0627\u0633\u0660");
|
||||
expect(ar.format(123)).toBe("\u0661\u0662\u0663\u0627\u0633\u0660");
|
||||
expect(ar.format(1234)).toBe("\u0661\u066b\u0662\u0663\u0664\u0627\u0633\u0663");
|
||||
expect(ar.format(12345)).toBe("\u0661\u0662\u066b\u0663\u0664\u0665\u0627\u0633\u0663");
|
||||
expect(ar.format(123456)).toBe(
|
||||
"\u0661\u0662\u0663\u066b\u0664\u0665\u0666\u0627\u0633\u0663"
|
||||
);
|
||||
expect(ar.format(1234567)).toBe("\u0661\u066b\u0662\u0663\u0665\u0627\u0633\u0666");
|
||||
expect(ar.format(0.1)).toBe("\u0661\u0660\u0660\u0627\u0633\u061c-\u0663");
|
||||
expect(ar.format(0.12)).toBe("\u0661\u0662\u0660\u0627\u0633\u061c-\u0663");
|
||||
expect(ar.format(1.23)).toBe("\u0661\u066b\u0662\u0663\u0627\u0633\u0660");
|
||||
});
|
||||
|
||||
test("notation=compact", () => {
|
||||
const en = new Intl.NumberFormat("en", { notation: "compact" });
|
||||
expect(en.format(1)).toBe("1");
|
||||
|
|
|
@ -244,6 +244,74 @@ describe("style=decimal", () => {
|
|||
{ type: "integer", value: "\u0661\u0662\u0663\u0664\u0665\u0666\u0667" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("notation=scientific", () => {
|
||||
const en = new Intl.NumberFormat("en", { notation: "scientific" });
|
||||
expect(en.formatToParts(12.3)).toEqual([
|
||||
{ type: "integer", value: "1" },
|
||||
{ type: "decimal", value: "." },
|
||||
{ type: "fraction", value: "23" },
|
||||
{ type: "exponentSeparator", value: "E" },
|
||||
{ type: "exponentInteger", value: "1" },
|
||||
]);
|
||||
expect(en.formatToParts(0.12)).toEqual([
|
||||
{ type: "integer", value: "1" },
|
||||
{ type: "decimal", value: "." },
|
||||
{ type: "fraction", value: "2" },
|
||||
{ type: "exponentSeparator", value: "E" },
|
||||
{ type: "exponentMinusSign", value: "-" },
|
||||
{ type: "exponentInteger", value: "1" },
|
||||
]);
|
||||
|
||||
const ar = new Intl.NumberFormat("ar", { notation: "scientific" });
|
||||
expect(ar.formatToParts(12.3)).toEqual([
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "decimal", value: "\u066b" },
|
||||
{ type: "fraction", value: "\u0662\u0663" },
|
||||
{ type: "exponentSeparator", value: "\u0627\u0633" },
|
||||
{ type: "exponentInteger", value: "\u0661" },
|
||||
]);
|
||||
expect(ar.formatToParts(0.12)).toEqual([
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "decimal", value: "\u066b" },
|
||||
{ type: "fraction", value: "\u0662" },
|
||||
{ type: "exponentSeparator", value: "\u0627\u0633" },
|
||||
{ type: "exponentMinusSign", value: "\u061c-" },
|
||||
{ type: "exponentInteger", value: "\u0661" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("notation=engineering", () => {
|
||||
const en = new Intl.NumberFormat("en", { notation: "engineering" });
|
||||
expect(en.formatToParts(1234)).toEqual([
|
||||
{ type: "integer", value: "1" },
|
||||
{ type: "decimal", value: "." },
|
||||
{ type: "fraction", value: "234" },
|
||||
{ type: "exponentSeparator", value: "E" },
|
||||
{ type: "exponentInteger", value: "3" },
|
||||
]);
|
||||
expect(en.formatToParts(0.12)).toEqual([
|
||||
{ type: "integer", value: "120" },
|
||||
{ type: "exponentSeparator", value: "E" },
|
||||
{ type: "exponentMinusSign", value: "-" },
|
||||
{ type: "exponentInteger", value: "3" },
|
||||
]);
|
||||
|
||||
const ar = new Intl.NumberFormat("ar", { notation: "engineering" });
|
||||
expect(ar.formatToParts(1234)).toEqual([
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "decimal", value: "\u066b" },
|
||||
{ type: "fraction", value: "\u0662\u0663\u0664" },
|
||||
{ type: "exponentSeparator", value: "\u0627\u0633" },
|
||||
{ type: "exponentInteger", value: "\u0663" },
|
||||
]);
|
||||
expect(ar.formatToParts(0.12)).toEqual([
|
||||
{ type: "integer", value: "\u0661\u0662\u0660" },
|
||||
{ type: "exponentSeparator", value: "\u0627\u0633" },
|
||||
{ type: "exponentMinusSign", value: "\u061c-" },
|
||||
{ type: "exponentInteger", value: "\u0663" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("style=percent", () => {
|
||||
|
|
Loading…
Reference in a new issue