diff options
4 files changed, 166 insertions, 17 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp index ff08d0d274..492cf73991 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp @@ -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. + 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); + + // 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). - // 3. Append a new Record { [[Type]]: "exponentInteger", [[Value]]: exponentResult.[[FormattedString]] } as the last element of result. + // Note: See the implementation of ToRawFixed for why we do not pass the 1. + auto exponent_result = to_raw_fixed(exponent, 0, 0); - // FIXME: Implement this when GetNotationSubPattern is fully implemented. + // 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; diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h index c186c326fb..2fded6977f 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.h @@ -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); 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 0dc3659349..4158eafa2b 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 @@ -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"); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.formatToParts.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.formatToParts.js index 4055ae8822..c6cfa7ed63 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.formatToParts.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.formatToParts.js @@ -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", () => { |