diff --git a/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp b/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp index edbe378ad10..88c22e79069 100644 --- a/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp +++ b/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -398,10 +399,10 @@ ThrowCompletionOr get_duration_unit_options(VM& vm, Duratio } // 1.1.7 ComputeFractionalDigits ( durationFormat, duration ), https://tc39.es/proposal-intl-duration-format/#sec-computefractionaldigits -double compute_fractional_digits(DurationFormat const& duration_format, DurationRecord const& duration) +Crypto::BigFraction compute_fractional_digits(DurationFormat const& duration_format, DurationRecord const& duration) { // 1. Let result be 0. - double result = 0; + Crypto::BigFraction result; // 2. Let exponent be 3. double exponent = 3; @@ -417,13 +418,14 @@ double compute_fractional_digits(DurationFormat const& duration_format, Duration VERIFY(first_is_one_of(duration_instances_component.unit, DurationFormat::Unit::Milliseconds, DurationFormat::Unit::Microseconds, DurationFormat::Unit::Nanoseconds)); // ii. Let value be the value of duration's field whose name is the Value Field value of the current row. - auto value = duration.*duration_instances_component.value_slot; - // iii. Set value to value / 10**exponent. - value = value / pow(10, exponent); + Crypto::BigFraction value { + Crypto::SignedBigInteger { duration.*duration_instances_component.value_slot }, + Crypto::UnsignedBigInteger { pow(10, exponent) } + }; // iv. Set result to result + value. - result += value; + result = result + value; // v. Set exponent to exponent + 3. exponent += 3; @@ -457,7 +459,7 @@ bool next_unit_fractional(DurationFormat const& duration_format, DurationFormat: } // 1.1.9 FormatNumericHours ( durationFormat, hoursValue, signDisplayed ), https://tc39.es/proposal-intl-duration-format/#sec-formatnumerichours -Vector format_numeric_hours(VM& vm, DurationFormat const& duration_format, double hours_value, bool sign_displayed) +Vector format_numeric_hours(VM& vm, DurationFormat const& duration_format, MathematicalValue const& hours_value, bool sign_displayed) { auto& realm = *vm.current_realm(); @@ -498,7 +500,7 @@ Vector format_numeric_hours(VM& vm, DurationFormat const& du auto number_format = construct_number_format(vm, duration_format, number_format_options); // 11. Let hoursParts be PartitionNumberPattern(nf, hoursValue). - auto hours_parts = partition_number_pattern(number_format, MathematicalValue { hours_value }); + auto hours_parts = partition_number_pattern(number_format, hours_value); // 12. For each Record { [[Type]], [[Value]] } part of hoursParts, do result.ensure_capacity(hours_parts.size()); @@ -513,7 +515,7 @@ Vector format_numeric_hours(VM& vm, DurationFormat const& du } // 1.1.10 FormatNumericMinutes ( durationFormat, minutesValue, hoursDisplayed, signDisplayed ), https://tc39.es/proposal-intl-duration-format/#sec-formatnumericminutes -Vector format_numeric_minutes(VM& vm, DurationFormat const& duration_format, double minutes_value, bool hours_displayed, bool sign_displayed) +Vector format_numeric_minutes(VM& vm, DurationFormat const& duration_format, MathematicalValue const& minutes_value, bool hours_displayed, bool sign_displayed) { auto& realm = *vm.current_realm(); @@ -563,7 +565,7 @@ Vector format_numeric_minutes(VM& vm, DurationFormat const& auto number_format = construct_number_format(vm, duration_format, number_format_options); // 12. Let minutesParts be PartitionNumberPattern(nf, minutesValue). - auto minutes_parts = partition_number_pattern(number_format, MathematicalValue { minutes_value }); + auto minutes_parts = partition_number_pattern(number_format, minutes_value); // 13. For each Record { [[Type]], [[Value]] } part of minutesParts, do result.ensure_capacity(result.size() + minutes_parts.size()); @@ -578,7 +580,7 @@ Vector format_numeric_minutes(VM& vm, DurationFormat const& } // 1.1.11 FormatNumericSeconds ( durationFormat, secondsValue, minutesDisplayed, signDisplayed ), https://tc39.es/proposal-intl-duration-format/#sec-formatnumericseconds -Vector format_numeric_seconds(VM& vm, DurationFormat const& duration_format, double seconds_value, bool minutes_displayed, bool sign_displayed) +Vector format_numeric_seconds(VM& vm, DurationFormat const& duration_format, MathematicalValue const& seconds_value, bool minutes_displayed, bool sign_displayed) { auto& realm = *vm.current_realm(); @@ -657,7 +659,7 @@ Vector format_numeric_seconds(VM& vm, DurationFormat const& auto number_format = construct_number_format(vm, duration_format, number_format_options); // 17. Let secondsParts be PartitionNumberPattern(nf, secondsValue). - auto seconds_parts = partition_number_pattern(number_format, MathematicalValue { seconds_value }); + auto seconds_parts = partition_number_pattern(number_format, seconds_value); // 18. For each Record { [[Type]], [[Value]] } part of secondsParts, do result.ensure_capacity(result.size() + seconds_parts.size()); @@ -693,12 +695,12 @@ Vector format_numeric_units(VM& vm, DurationFormat const& du auto minutes_display = duration_format.minutes_display(); // 7. Let secondsValue be duration.[[Seconds]]. - auto seconds_value = duration.seconds; + Crypto::BigFraction seconds_value { duration.seconds }; // 8. If duration.[[Milliseconds]] is not 0 or duration.[[Microseconds]] is not 0 or duration.[[Nanoseconds]] is not 0, then if (duration.milliseconds != 0 || duration.microseconds != 0 || duration.nanoseconds != 0) { // a. Set secondsValue to secondsValue + ComputeFractionalDigits(durationFormat, duration). - seconds_value += compute_fractional_digits(duration_format, duration); + seconds_value = seconds_value + compute_fractional_digits(duration_format, duration); } // 9. Let secondsDisplay be durationFormat.[[SecondsDisplay]]. @@ -720,7 +722,7 @@ Vector format_numeric_units(VM& vm, DurationFormat const& du // a. Let secondsFormatted be true. // 13. Else, // a. Let secondsFormatted be false. - auto seconds_formatted = seconds_value != 0 || seconds_display == DurationFormat::Display::Always; + auto seconds_formatted = !seconds_value.is_zero() || seconds_display == DurationFormat::Display::Always; // 14. Let minutesFormatted be false. auto minutes_formatted = false; @@ -741,17 +743,19 @@ Vector format_numeric_units(VM& vm, DurationFormat const& du // 16. If hoursFormatted is true, then if (hours_formatted) { + MathematicalValue hours_mv { hours_value }; + // a. If signDisplayed is true, then if (sign_displayed) { // i. If hoursValue is 0 and DurationSign(duration) is -1, then if (hours_value == 0 && duration_sign(duration) == -1) { // 1. Set hoursValue to NEGATIVE-ZERO. - hours_value = -0.0; + hours_mv = MathematicalValue { MathematicalValue::Symbol::NegativeZero }; } } // b. Let hoursParts be FormatNumericHours(durationFormat, hoursValue, signDisplayed). - auto hours_parts = format_numeric_hours(vm, duration_format, hours_value, sign_displayed); + auto hours_parts = format_numeric_hours(vm, duration_format, hours_mv, sign_displayed); // b. Set numericPartsList to the list-concatenation of numericPartsList and hoursParts. numeric_parts_list.extend(move(hours_parts)); @@ -762,17 +766,19 @@ Vector format_numeric_units(VM& vm, DurationFormat const& du // 17. If minutesFormatted is true, then if (minutes_formatted) { + MathematicalValue minutes_mv { minutes_value }; + // a. If signDisplayed is true, then if (sign_displayed) { // i. If minutesValue is 0 and DurationSign(duration) is -1, then if (minutes_value == 0 && duration_sign(duration) == -1) { // 1. Set minutesValue to NEGATIVE-ZERO. - minutes_value = -0.0; + minutes_mv = MathematicalValue { MathematicalValue::Symbol::NegativeZero }; } } // b. Let minutesParts be FormatNumericMinutes(durationFormat, minutesValue, hoursFormatted, signDisplayed). - auto minutes_parts = format_numeric_minutes(vm, duration_format, minutes_value, hours_formatted, sign_displayed); + auto minutes_parts = format_numeric_minutes(vm, duration_format, minutes_mv, hours_formatted, sign_displayed); // c. Set numericPartsList to the list-concatenation of numericPartsList and minutesParts. numeric_parts_list.extend(move(minutes_parts)); @@ -784,7 +790,7 @@ Vector format_numeric_units(VM& vm, DurationFormat const& du // 18. If secondsFormatted is true, then if (seconds_formatted) { // a. Let secondsParts be FormatNumericSeconds(durationFormat, secondsValue, minutesFormatted, signDisplayed). - auto seconds_parts = format_numeric_seconds(vm, duration_format, seconds_value, minutes_formatted, sign_displayed); + auto seconds_parts = format_numeric_seconds(vm, duration_format, MathematicalValue { seconds_value.to_string(9) }, minutes_formatted, sign_displayed); // b. Set numericPartsList to the list-concatenation of numericPartsList and secondsParts. numeric_parts_list.extend(move(seconds_parts)); @@ -904,7 +910,7 @@ Vector partition_duration_format_pattern(VM& vm, DurationFor auto const& duration_instances_component = duration_instances_components[i]; // a. Let value be the value of duration's field whose name is the Value Field value of the current row. - auto value = duration.*duration_instances_component.value_slot; + Crypto::BigFraction value { duration.*duration_instances_component.value_slot }; // b. Let style be the value of durationFormat's internal slot whose name is the Style Slot value of the current row. auto style = (duration_format.*duration_instances_component.get_style_slot)(); @@ -941,7 +947,7 @@ Vector partition_duration_format_pattern(VM& vm, DurationFor // 1. If NextUnitFractional(durationFormat, unit) is true, then if (next_unit_fractional(duration_format, unit)) { // a. Set value to value + ComputeFractionalDigits(durationFormat, duration). - value += compute_fractional_digits(duration_format, duration); + value = value + compute_fractional_digits(duration_format, duration); u8 maximum_fraction_digits = 0; u8 minimum_fraction_digits = 0; @@ -978,7 +984,9 @@ Vector partition_duration_format_pattern(VM& vm, DurationFor } // iii. If value is not 0 or display is "always", then - if (value != 0 || display == DurationFormat::Display::Always) { + if (!value.is_zero() || display == DurationFormat::Display::Always) { + MathematicalValue value_mv { value.to_string(9) }; + // 1. Let numberingSystem be durationFormat.[[NumberingSystem]]. auto const& numbering_system = duration_format.numbering_system(); @@ -991,9 +999,9 @@ Vector partition_duration_format_pattern(VM& vm, DurationFor sign_displayed = false; // b. If value is 0 and DurationSign(duration) is -1, then - if (value == 0 && duration_sign(duration) == -1) { + if (value.is_zero() && duration_sign(duration) == -1) { // i. Set value to NEGATIVE-ZERO. - value = -0.0; + value_mv = MathematicalValue { MathematicalValue::Symbol::NegativeZero }; } } // 4. Else, @@ -1019,7 +1027,7 @@ Vector partition_duration_format_pattern(VM& vm, DurationFor auto number_format = construct_number_format(vm, duration_format, number_format_options); // 10. Let parts be PartitionNumberPattern(nf, value). - auto parts = partition_number_pattern(number_format, MathematicalValue { value }); + auto parts = partition_number_pattern(number_format, value_mv); // 11. Let list be a new empty List. Vector list; diff --git a/Libraries/LibJS/Runtime/Intl/DurationFormat.h b/Libraries/LibJS/Runtime/Intl/DurationFormat.h index 62695788c9d..7e564c224dc 100644 --- a/Libraries/LibJS/Runtime/Intl/DurationFormat.h +++ b/Libraries/LibJS/Runtime/Intl/DurationFormat.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -263,11 +264,11 @@ struct DurationFormatPart { ThrowCompletionOr to_duration_record(VM&, Value input); i8 duration_sign(DurationRecord const&); ThrowCompletionOr get_duration_unit_options(VM&, DurationFormat::Unit unit, Object const& options, DurationFormat::Style base_style, ReadonlySpan styles_list, DurationFormat::ValueStyle digital_base, Optional previous_style, bool two_digit_hours); -double compute_fractional_digits(DurationFormat const&, DurationRecord const&); +Crypto::BigFraction compute_fractional_digits(DurationFormat const&, DurationRecord const&); bool next_unit_fractional(DurationFormat const&, DurationFormat::Unit unit); -Vector format_numeric_hours(VM&, DurationFormat const&, double hours_value, bool sign_displayed); -Vector format_numeric_minutes(VM&, DurationFormat const&, double minutes_value, bool hours_displayed, bool sign_displayed); -Vector format_numeric_seconds(VM&, DurationFormat const&, double seconds_value, bool minutes_displayed, bool sign_displayed); +Vector format_numeric_hours(VM&, DurationFormat const&, MathematicalValue const& hours_value, bool sign_displayed); +Vector format_numeric_minutes(VM&, DurationFormat const&, MathematicalValue const& minutes_value, bool hours_displayed, bool sign_displayed); +Vector format_numeric_seconds(VM&, DurationFormat const&, MathematicalValue const& seconds_value, bool minutes_displayed, bool sign_displayed); Vector format_numeric_units(VM&, DurationFormat const&, DurationRecord const&, DurationFormat::Unit first_numeric_unit, bool sign_displayed); Vector list_format_parts(VM&, DurationFormat const&, Vector>& partitioned_parts_list); Vector partition_duration_format_pattern(VM&, DurationFormat const&, DurationRecord const&); diff --git a/Libraries/LibJS/Tests/builtins/Intl/DurationFormat/DurationFormat.prototype.format.js b/Libraries/LibJS/Tests/builtins/Intl/DurationFormat/DurationFormat.prototype.format.js index 4d9afa5d1d6..6774b6d991e 100644 --- a/Libraries/LibJS/Tests/builtins/Intl/DurationFormat/DurationFormat.prototype.format.js +++ b/Libraries/LibJS/Tests/builtins/Intl/DurationFormat/DurationFormat.prototype.format.js @@ -145,6 +145,24 @@ describe("correct behavior", () => { const de = new Intl.DurationFormat("de", { style: "digital" }); expect(de.format(duration)).toBe("123456:456789:789123"); }); + + test("precise mathematical values", () => { + const en = new Intl.DurationFormat("en", { style: "digital" }); + + let duration = { + seconds: 10000000, + nanoseconds: 1, + }; + expect(en.format(duration)).toBe("0:00:10000000.000000001"); + + duration = { + seconds: 1, + milliseconds: 2, + microseconds: 3, + nanoseconds: Number.MAX_SAFE_INTEGER, + }; + expect(en.format(duration)).toBe("0:00:9007200.256743991"); + }); }); describe("errors", () => {