diff options
author | Linus Groh <mail@linusgroh.de> | 2022-05-24 18:26:13 +0100 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2022-05-25 00:25:23 +0100 |
commit | ae1fdf299d8c92062ad53a71f614d68ce37ee1d8 (patch) | |
tree | d4da8d4a382166dbad3313ad3c09040d613402b9 /Userland/Libraries/LibJS/Runtime/Temporal | |
parent | 93a20b3bfb23e122eccf817b733625d8599b6614 (diff) | |
download | serenity-ae1fdf299d8c92062ad53a71f614d68ce37ee1d8.zip |
LibJS: Use the rounding abstract operations from Intl.NumberFormat V3
This is an editorial change in the Temporal spec.
See: https://github.com/tc39/proposal-temporal/commit/75279e5
Co-Authored-By: Idan Horowitz <idan.horowitz@gmail.com>
Diffstat (limited to 'Userland/Libraries/LibJS/Runtime/Temporal')
7 files changed, 304 insertions, 111 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index a4d8ad4765..f35ed5b29e 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> + * Copyright (c) 2021-2022, Idan Horowitz <idan.horowitz@serenityos.org> * Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org> * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org> * @@ -10,6 +10,7 @@ #include <AK/DateTimeLexer.h> #include <AK/TypeCasts.h> #include <AK/Variant.h> +#include <LibJS/Runtime/AbstractOperations.h> #include <LibJS/Runtime/Completion.h> #include <LibJS/Runtime/IteratorOperations.h> #include <LibJS/Runtime/PropertyKey.h> @@ -978,103 +979,282 @@ String format_seconds_string_part(u8 second, u16 millisecond, u16 microsecond, u return String::formatted("{}.{}", seconds_string, fraction_string); } -// NOTE: We have two variants of this function, one using doubles and one using BigInts - most of the time +// 13.28 GetUnsignedRoundingMode ( roundingMode, isNegative ), https://tc39.es/proposal-temporal/#sec-temporal-getunsignedroundingmode +UnsignedRoundingMode get_unsigned_rounding_mode(StringView rounding_mode, bool is_negative) +{ + // 1. If isNegative is true, return the specification type in the third column of Table 14 where the first column is roundingMode and the second column is "negative". + if (is_negative) { + if (rounding_mode == "ceil"sv) + return UnsignedRoundingMode::Zero; + if (rounding_mode == "floor"sv) + return UnsignedRoundingMode::Infinity; + if (rounding_mode == "expand"sv) + return UnsignedRoundingMode::Infinity; + if (rounding_mode == "trunc"sv) + return UnsignedRoundingMode::Zero; + if (rounding_mode == "halfCeil"sv) + return UnsignedRoundingMode::HalfZero; + if (rounding_mode == "halfFloor"sv) + return UnsignedRoundingMode::HalfInfinity; + if (rounding_mode == "halfExpand"sv) + return UnsignedRoundingMode::HalfInfinity; + if (rounding_mode == "halfTrunc"sv) + return UnsignedRoundingMode::HalfZero; + if (rounding_mode == "halfEven"sv) + return UnsignedRoundingMode::HalfEven; + VERIFY_NOT_REACHED(); + } + // 2. Else, return the specification type in the third column of Table 14 where the first column is roundingMode and the second column is "positive". + else { + if (rounding_mode == "ceil"sv) + return UnsignedRoundingMode::Infinity; + if (rounding_mode == "floor"sv) + return UnsignedRoundingMode::Zero; + if (rounding_mode == "expand"sv) + return UnsignedRoundingMode::Infinity; + if (rounding_mode == "trunc"sv) + return UnsignedRoundingMode::Zero; + if (rounding_mode == "halfCeil"sv) + return UnsignedRoundingMode::HalfInfinity; + if (rounding_mode == "halfFloor"sv) + return UnsignedRoundingMode::HalfZero; + if (rounding_mode == "halfExpand"sv) + return UnsignedRoundingMode::HalfInfinity; + if (rounding_mode == "halfTrunc"sv) + return UnsignedRoundingMode::HalfZero; + if (rounding_mode == "halfEven"sv) + return UnsignedRoundingMode::HalfEven; + VERIFY_NOT_REACHED(); + } +} + +// NOTE: We have two variants of these functions, one using doubles and one using BigInts - most of the time // doubles will be fine, but take care to choose the right one. The spec is not very clear about this, as // it uses mathematical values which can be arbitrarily (but not infinitely) large. // Incidentally V8's Temporal implementation does the same :^) -// 13.29 RoundNumberToIncrement ( x, increment, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement -i64 round_number_to_increment(double x, u64 increment, StringView rounding_mode) +// 13.29 ApplyUnsignedRoundingMode ( x, r1, r2, unsignedRoundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-applyunsignedroundingmode +double apply_unsigned_rounding_mode(double x, double r1, double r2, Optional<UnsignedRoundingMode> const& unsigned_rounding_mode) +{ + // 1. If x is equal to r1, return r1. + if (x == r1) + return r1; + + // 2. Assert: r1 < x < r2. + VERIFY(r1 < x && x < r2); + + // 3. Assert: unsignedRoundingMode is not undefined. + VERIFY(unsigned_rounding_mode.has_value()); + + // 4. If unsignedRoundingMode is zero, return r1. + if (unsigned_rounding_mode == UnsignedRoundingMode::Zero) + return r1; + + // 5. If unsignedRoundingMode is infinity, return r2. + if (unsigned_rounding_mode == UnsignedRoundingMode::Infinity) + return r2; + + // 6. Let d1 be x – r1. + auto d1 = x - r1; + + // 7. Let d2 be r2 – x. + auto d2 = r2 - x; + + // 8. If d1 < d2, return r1. + if (d1 < d2) + return r1; + + // 9. If d2 < d1, return r2. + if (d2 < d1) + return r2; + + // 10. Assert: d1 is equal to d2. + VERIFY(d1 == d2); + + // 11. If unsignedRoundingMode is half-zero, return r1. + if (unsigned_rounding_mode == UnsignedRoundingMode::HalfZero) + return r1; + + // 12. If unsignedRoundingMode is half-infinity, return r2. + if (unsigned_rounding_mode == UnsignedRoundingMode::HalfInfinity) + return r2; + + // 13. Assert: unsignedRoundingMode is half-even. + VERIFY(unsigned_rounding_mode == UnsignedRoundingMode::HalfEven); + + // 14. Let cardinality be (r1 / (r2 – r1)) modulo 2. + auto cardinality = modulo((r1 / (r2 - r1)), 2); + + // 15. If cardinality is 0, return r1. + if (cardinality == 0) + return r1; + + // 16. Return r2. + return r2; +} + +// 13.29 ApplyUnsignedRoundingMode ( x, r1, r2, unsignedRoundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-applyunsignedroundingmode +Crypto::SignedBigInteger apply_unsigned_rounding_mode(Crypto::SignedDivisionResult const& x, Crypto::SignedBigInteger const& r1, Crypto::SignedBigInteger const& r2, Optional<UnsignedRoundingMode> const& unsigned_rounding_mode, Crypto::UnsignedBigInteger const& increment) +{ + // 1. If x is equal to r1, return r1. + if (x.quotient == r1 && x.remainder.unsigned_value().is_zero()) + return r1; + + // 2. Assert: r1 < x < r2. + // NOTE: Skipped for the sake of performance + + // 3. Assert: unsignedRoundingMode is not undefined. + VERIFY(unsigned_rounding_mode.has_value()); + + // 4. If unsignedRoundingMode is zero, return r1. + if (unsigned_rounding_mode == UnsignedRoundingMode::Zero) + return r1; + + // 5. If unsignedRoundingMode is infinity, return r2. + if (unsigned_rounding_mode == UnsignedRoundingMode::Infinity) + return r2; + + // 6. Let d1 be x – r1. + auto d1 = x.remainder.unsigned_value(); + + // 7. Let d2 be r2 – x. + auto d2 = increment.minus(x.remainder.unsigned_value()); + + // 8. If d1 < d2, return r1. + if (d1 < d2) + return r1; + + // 9. If d2 < d1, return r2. + if (d2 < d1) + return r2; + + // 10. Assert: d1 is equal to d2. + // NOTE: Skipped for the sake of performance + + // 11. If unsignedRoundingMode is half-zero, return r1. + if (unsigned_rounding_mode == UnsignedRoundingMode::HalfZero) + return r1; + + // 12. If unsignedRoundingMode is half-infinity, return r2. + if (unsigned_rounding_mode == UnsignedRoundingMode::HalfInfinity) + return r2; + + // 13. Assert: unsignedRoundingMode is half-even. + VERIFY(unsigned_rounding_mode == UnsignedRoundingMode::HalfEven); + + // 14. Let cardinality be (r1 / (r2 – r1)) modulo 2. + auto cardinality = modulo(r1.divided_by(r2.minus(r1)).quotient, "2"_bigint); + + // 15. If cardinality is 0, return r1. + if (cardinality.unsigned_value().is_zero()) + return r1; + + // 16. Return r2. + return r2; +} + +// 13.30 RoundNumberToIncrement ( x, increment, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement +double round_number_to_increment(double x, u64 increment, StringView rounding_mode) { - // 1. Assert: x and increment are mathematical values. - // 2. Assert: roundingMode is "ceil", "floor", "trunc", or "halfExpand". VERIFY(rounding_mode == "ceil"sv || rounding_mode == "floor"sv || rounding_mode == "trunc"sv || rounding_mode == "halfExpand"sv); - // 3. Let quotient be x / increment. - auto quotient = x / (double)increment; + // 1. Let quotient be x / increment. + auto quotient = x / static_cast<double>(increment); - double rounded; + bool is_negative; - // 4. If roundingMode is "ceil", then - if (rounding_mode == "ceil"sv) { - // a. Let rounded be -floor(-quotient). - rounded = -floor(-quotient); - } - // 5. Else if roundingMode is "floor", then - else if (rounding_mode == "floor"sv) { - // a. Let rounded be floor(quotient). - rounded = floor(quotient); - } - // 6. Else if roundingMode is "trunc", then - else if (rounding_mode == "trunc"sv) { - // a. Let rounded be RoundTowardsZero(quotient). - rounded = trunc(quotient); + // 2. If quotient < 0, then + if (quotient < 0) { + // a. Let isNegative be true. + is_negative = true; + + // b. Set quotient to -quotient. + quotient = -quotient; } - // 7. Else, + // 3. Else, else { - // a. Let rounded be ! RoundHalfAwayFromZero(quotient). - rounded = round(quotient); + // a. Let isNegative be false. + is_negative = false; } - // 8. Return rounded × increment. - return (i64)rounded * (i64)increment; + // 4. Let unsignedRoundingMode be GetUnsignedRoundingMode(roundingMode, isNegative). + auto unsigned_rounding_mode = get_unsigned_rounding_mode(rounding_mode, is_negative); + + // 5. Let r1 be the largest integer such that r1 ≤ quotient. + auto r1 = floor(quotient); + + // 6. Let r2 be the smallest integer such that r2 > quotient. + auto r2 = ceil(quotient); + if (quotient == r2) + r2++; + + // 7. Let rounded be ApplyUnsignedRoundingMode(quotient, r1, r2, unsignedRoundingMode). + auto rounded = apply_unsigned_rounding_mode(quotient, r1, r2, unsigned_rounding_mode); + + // 8. If isNegative is true, set rounded to -rounded. + if (is_negative) + rounded = -rounded; + + // 9. Return rounded × increment. + return rounded * static_cast<double>(increment); } -// 13.29 RoundNumberToIncrement ( x, increment, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement -BigInt* round_number_to_increment(GlobalObject& global_object, BigInt const& x, u64 increment, StringView rounding_mode) +// 13.30 RoundNumberToIncrement ( x, increment, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement +Crypto::SignedBigInteger round_number_to_increment(Crypto::SignedBigInteger const& x, u64 increment, StringView rounding_mode) { - auto& heap = global_object.heap(); - - // 1. Assert: x and increment are mathematical values. - // 2. Assert: roundingMode is "ceil", "floor", "trunc", or "halfExpand". VERIFY(rounding_mode == "ceil"sv || rounding_mode == "floor"sv || rounding_mode == "trunc"sv || rounding_mode == "halfExpand"sv); // OPTIMIZATION: If the increment is 1 the number is always rounded if (increment == 1) - return js_bigint(heap, x.big_integer()); + return x; auto increment_big_int = Crypto::UnsignedBigInteger::create_from(increment); - // 3. Let quotient be x / increment. - auto division_result = x.big_integer().divided_by(increment_big_int); + + // 1. Let quotient be x / increment. + auto division_result = x.divided_by(increment_big_int); // OPTIMIZATION: If there's no remainder the number is already rounded - if (division_result.remainder == Crypto::UnsignedBigInteger { 0 }) - return js_bigint(heap, x.big_integer()); - - Crypto::SignedBigInteger rounded = move(division_result.quotient); - // 4. If roundingMode is "ceil", then - if (rounding_mode == "ceil"sv) { - // a. Let rounded be -floor(-quotient). - if (!division_result.remainder.is_negative()) - rounded = rounded.plus(Crypto::UnsignedBigInteger { 1 }); - } - // 5. Else if roundingMode is "floor", then - else if (rounding_mode == "floor"sv) { - // a. Let rounded be floor(quotient). - if (division_result.remainder.is_negative()) - rounded = rounded.minus(Crypto::UnsignedBigInteger { 1 }); - } - // 6. Else if roundingMode is "trunc", then - else if (rounding_mode == "trunc"sv) { - // a. Let rounded be the RoundTowardsZero(quotient). - // NOTE: This is a no-op + if (division_result.remainder.unsigned_value().is_zero()) + return x; + + bool is_negative; + + // 2. If quotient < 0, then + if (division_result.quotient.is_negative()) { + // a. Let isNegative be true. + is_negative = true; + + // b. Set quotient to -quotient. + division_result.quotient.negate(); + division_result.remainder.negate(); } - // 7. Else, + // 3. Else, else { - // a. Let rounded be ! RoundHalfAwayFromZero(quotient). - if (division_result.remainder.multiplied_by(Crypto::UnsignedBigInteger { 2 }).unsigned_value() >= increment_big_int) { - if (division_result.remainder.is_negative()) - rounded = rounded.minus(Crypto::UnsignedBigInteger { 1 }); - else - rounded = rounded.plus(Crypto::UnsignedBigInteger { 1 }); - } + // a. Let isNegative be false. + is_negative = false; } - // 8. Return rounded × increment. - return js_bigint(heap, rounded.multiplied_by(increment_big_int)); + // 4. Let unsignedRoundingMode be GetUnsignedRoundingMode(roundingMode, isNegative). + auto unsigned_rounding_mode = get_unsigned_rounding_mode(rounding_mode, is_negative); + + // 5. Let r1 be the largest integer such that r1 ≤ quotient. + auto r1 = division_result.quotient; + + // 6. Let r2 be the smallest integer such that r2 > quotient. + auto r2 = division_result.quotient.plus("1"_bigint); + + // 7. Let rounded be ApplyUnsignedRoundingMode(quotient, r1, r2, unsignedRoundingMode). + auto rounded = apply_unsigned_rounding_mode(division_result, r1, r2, unsigned_rounding_mode, increment_big_int); + + // 8. If isNegative is true, set rounded to -rounded. + if (is_negative) + rounded.negate(); + + // 9. Return rounded × increment. + return rounded.multiplied_by(increment_big_int); } -// 13.31 ParseISODateTime ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parseisodatetime +// 13.32 ParseISODateTime ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parseisodatetime ThrowCompletionOr<ISODateTime> parse_iso_date_time(GlobalObject& global_object, ParseResult const& parse_result) { auto& vm = global_object.vm(); @@ -1199,7 +1379,7 @@ ThrowCompletionOr<ISODateTime> parse_iso_date_time(GlobalObject& global_object, return ISODateTime { .year = year, .month = month, .day = day, .hour = hour, .minute = minute, .second = second, .millisecond = millisecond, .microsecond = microsecond, .nanosecond = nanosecond, .calendar = Optional<String>(move(calendar_part)) }; } -// 13.32 ParseTemporalInstantString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalinstantstring +// 13.33 ParseTemporalInstantString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalinstantstring ThrowCompletionOr<TemporalInstant> parse_temporal_instant_string(GlobalObject& global_object, String const& iso_string) { auto& vm = global_object.vm(); @@ -1235,7 +1415,7 @@ ThrowCompletionOr<TemporalInstant> parse_temporal_instant_string(GlobalObject& g return TemporalInstant { .year = result.year, .month = result.month, .day = result.day, .hour = result.hour, .minute = result.minute, .second = result.second, .millisecond = result.millisecond, .microsecond = result.microsecond, .nanosecond = result.nanosecond, .time_zone_offset = move(offset_string) }; } -// 13.33 ParseTemporalZonedDateTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalzoneddatetimestring +// 13.34 ParseTemporalZonedDateTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalzoneddatetimestring ThrowCompletionOr<TemporalZonedDateTime> parse_temporal_zoned_date_time_string(GlobalObject& global_object, String const& iso_string) { auto& vm = global_object.vm(); @@ -1261,7 +1441,7 @@ ThrowCompletionOr<TemporalZonedDateTime> parse_temporal_zoned_date_time_string(G return TemporalZonedDateTime { .date_time = move(result), .time_zone = move(time_zone_result) }; } -// 13.34 ParseTemporalCalendarString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalcalendarstring +// 13.35 ParseTemporalCalendarString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalcalendarstring ThrowCompletionOr<String> parse_temporal_calendar_string(GlobalObject& global_object, String const& iso_string) { auto& vm = global_object.vm(); @@ -1288,7 +1468,7 @@ ThrowCompletionOr<String> parse_temporal_calendar_string(GlobalObject& global_ob return id_part.value(); } -// 13.35 ParseTemporalDateString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldatestring +// 13.36 ParseTemporalDateString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldatestring ThrowCompletionOr<TemporalDate> parse_temporal_date_string(GlobalObject& global_object, String const& iso_string) { // 1. Let parts be ? ParseTemporalDateTimeString(isoString). @@ -1298,7 +1478,7 @@ ThrowCompletionOr<TemporalDate> parse_temporal_date_string(GlobalObject& global_ return TemporalDate { .year = parts.year, .month = parts.month, .day = parts.day, .calendar = move(parts.calendar) }; } -// 13.36 ParseTemporalDateTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldatetimestring +// 13.37 ParseTemporalDateTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldatetimestring ThrowCompletionOr<ISODateTime> parse_temporal_date_time_string(GlobalObject& global_object, String const& iso_string) { auto& vm = global_object.vm(); @@ -1322,7 +1502,7 @@ ThrowCompletionOr<ISODateTime> parse_temporal_date_time_string(GlobalObject& glo return parse_iso_date_time(global_object, *parse_result); } -// 13.37 ParseTemporalDurationString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldurationstring +// 13.38 ParseTemporalDurationString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldurationstring ThrowCompletionOr<DurationRecord> parse_temporal_duration_string(GlobalObject& global_object, String const& iso_string) { auto& vm = global_object.vm(); @@ -1462,7 +1642,7 @@ ThrowCompletionOr<DurationRecord> parse_temporal_duration_string(GlobalObject& g return create_duration_record(global_object, years * factor, months * factor, weeks * factor, days * factor, hours * factor, floor(minutes) * factor, floor(seconds) * factor, floor(milliseconds) * factor, floor(microseconds) * factor, floor(nanoseconds) * factor); } -// 13.38 ParseTemporalMonthDayString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalmonthdaystring +// 13.39 ParseTemporalMonthDayString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalmonthdaystring ThrowCompletionOr<TemporalMonthDay> parse_temporal_month_day_string(GlobalObject& global_object, String const& iso_string) { auto& vm = global_object.vm(); @@ -1498,7 +1678,7 @@ ThrowCompletionOr<TemporalMonthDay> parse_temporal_month_day_string(GlobalObject return TemporalMonthDay { .year = year, .month = result.month, .day = result.day, .calendar = move(result.calendar) }; } -// 13.39 ParseTemporalRelativeToString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalrelativetostring +// 13.40 ParseTemporalRelativeToString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalrelativetostring ThrowCompletionOr<TemporalZonedDateTime> parse_temporal_relative_to_string(GlobalObject& global_object, String const& iso_string) { auto& vm = global_object.vm(); @@ -1547,7 +1727,7 @@ ThrowCompletionOr<TemporalZonedDateTime> parse_temporal_relative_to_string(Globa return TemporalZonedDateTime { .date_time = move(result), .time_zone = { .z = z, .offset_string = move(offset_string), .name = move(time_zone) } }; } -// 13.40 ParseTemporalTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimestring +// 13.41 ParseTemporalTimeString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimestring ThrowCompletionOr<TemporalTime> parse_temporal_time_string(GlobalObject& global_object, String const& iso_string) { auto& vm = global_object.vm(); @@ -1576,7 +1756,7 @@ ThrowCompletionOr<TemporalTime> parse_temporal_time_string(GlobalObject& global_ return TemporalTime { .hour = result.hour, .minute = result.minute, .second = result.second, .millisecond = result.millisecond, .microsecond = result.microsecond, .nanosecond = result.nanosecond, .calendar = move(result.calendar) }; } -// 13.41 ParseTemporalTimeZoneString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimezonestring +// 13.42 ParseTemporalTimeZoneString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimezonestring ThrowCompletionOr<TemporalTimeZone> parse_temporal_time_zone_string(GlobalObject& global_object, String const& iso_string) { auto& vm = global_object.vm(); @@ -1619,7 +1799,7 @@ ThrowCompletionOr<TemporalTimeZone> parse_temporal_time_zone_string(GlobalObject return TemporalTimeZone { .z = false, .offset_string = Optional<String>(move(offset_string)), .name = Optional<String>(move(name)) }; } -// 13.42 ParseTemporalYearMonthString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalyearmonthstring +// 13.43 ParseTemporalYearMonthString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalyearmonthstring ThrowCompletionOr<TemporalYearMonth> parse_temporal_year_month_string(GlobalObject& global_object, String const& iso_string) { auto& vm = global_object.vm(); @@ -1646,7 +1826,7 @@ ThrowCompletionOr<TemporalYearMonth> parse_temporal_year_month_string(GlobalObje return TemporalYearMonth { .year = result.year, .month = result.month, .day = result.day, .calendar = move(result.calendar) }; } -// 13.43 ToPositiveInteger ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-topositiveinteger +// 13.44 ToPositiveInteger ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-topositiveinteger ThrowCompletionOr<double> to_positive_integer(GlobalObject& global_object, Value argument) { auto& vm = global_object.vm(); @@ -1664,7 +1844,7 @@ ThrowCompletionOr<double> to_positive_integer(GlobalObject& global_object, Value return integer; } -// 13.46 PrepareTemporalFields ( fields, fieldNames, requiredFields ), https://tc39.es/proposal-temporal/#sec-temporal-preparetemporalfields +// 13.47 PrepareTemporalFields ( fields, fieldNames, requiredFields ), https://tc39.es/proposal-temporal/#sec-temporal-preparetemporalfields ThrowCompletionOr<Object*> prepare_temporal_fields(GlobalObject& global_object, Object const& fields, Vector<String> const& field_names, Vector<StringView> const& required_fields) { auto& vm = global_object.vm(); @@ -1715,7 +1895,7 @@ ThrowCompletionOr<Object*> prepare_temporal_fields(GlobalObject& global_object, return result; } -// 13.47 PreparePartialTemporalFields ( fields, fieldNames ), https://tc39.es/proposal-temporal/#sec-temporal-preparepartialtemporalfields +// 13.48 PreparePartialTemporalFields ( fields, fieldNames ), https://tc39.es/proposal-temporal/#sec-temporal-preparepartialtemporalfields ThrowCompletionOr<Object*> prepare_partial_temporal_fields(GlobalObject& global_object, Object const& fields, Vector<String> const& field_names) { auto& vm = global_object.vm(); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h index 2e173b25f9..4217814adc 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h @@ -27,6 +27,14 @@ enum class DifferenceOperation { Until, }; +enum class UnsignedRoundingMode { + HalfEven, + HalfInfinity, + HalfZero, + Infinity, + Zero, +}; + enum class OptionType { Boolean, String, @@ -135,8 +143,11 @@ ThrowCompletionOr<void> reject_object_with_calendar_or_time_zone(GlobalObject&, String format_seconds_string_part(u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Variant<StringView, u8> const& precision); double sign(double); double sign(Crypto::SignedBigInteger const&); -i64 round_number_to_increment(double, u64 increment, StringView rounding_mode); -BigInt* round_number_to_increment(GlobalObject&, BigInt const&, u64 increment, StringView rounding_mode); +UnsignedRoundingMode get_unsigned_rounding_mode(StringView rounding_mode, bool is_negative); +double apply_unsigned_rounding_mode(double x, double r1, double r2, Optional<UnsignedRoundingMode> const&); +Crypto::SignedBigInteger apply_unsigned_rounding_mode(Crypto::SignedDivisionResult const&, Crypto::SignedBigInteger const& r1, Crypto::SignedBigInteger const& r2, Optional<UnsignedRoundingMode> const&, Crypto::UnsignedBigInteger const& increment); +double round_number_to_increment(double, u64 increment, StringView rounding_mode); +Crypto::SignedBigInteger round_number_to_increment(Crypto::SignedBigInteger const&, u64 increment, StringView rounding_mode); ThrowCompletionOr<ISODateTime> parse_iso_date_time(GlobalObject&, ParseResult const& parse_result); ThrowCompletionOr<TemporalInstant> parse_temporal_instant_string(GlobalObject&, String const& iso_string); ThrowCompletionOr<TemporalZonedDateTime> parse_temporal_zoned_date_time_string(GlobalObject&, String const& iso_string); @@ -153,7 +164,7 @@ ThrowCompletionOr<double> to_positive_integer(GlobalObject&, Value argument); ThrowCompletionOr<Object*> prepare_temporal_fields(GlobalObject&, Object const& fields, Vector<String> const& field_names, Vector<StringView> const& required_fields); ThrowCompletionOr<Object*> prepare_partial_temporal_fields(GlobalObject&, Object const& fields, Vector<String> const& field_names); -// 13.44 ToIntegerThrowOnInfinity ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-tointegerthrowoninfinity +// 13.45 ToIntegerThrowOnInfinity ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-tointegerthrowoninfinity template<typename... Args> ThrowCompletionOr<double> to_integer_throw_on_infinity(GlobalObject& global_object, Value argument, ErrorType error_type, Args... args) { @@ -172,7 +183,7 @@ ThrowCompletionOr<double> to_integer_throw_on_infinity(GlobalObject& global_obje return integer; } -// 13.45 ToIntegerWithoutRounding ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-tointegerwithoutrounding +// 13.46 ToIntegerWithoutRounding ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-tointegerwithoutrounding template<typename... Args> ThrowCompletionOr<double> to_integer_without_rounding(GlobalObject& global_object, Value argument, ErrorType error_type, Args... args) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp index 7024a72388..daebd1d4e1 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp @@ -1278,8 +1278,8 @@ ThrowCompletionOr<RoundedDuration> round_duration(GlobalObject& global_object, d // y. Let fractionalYears be years + days / abs(oneYearDays). auto fractional_years = years + days / fabs(one_year_days); - // z. Set years to ! RoundNumberToIncrement(fractionalYears, increment, roundingMode). - years = (double)round_number_to_increment(fractional_years, increment, rounding_mode); + // z. Set years to RoundNumberToIncrement(fractionalYears, increment, roundingMode). + years = round_number_to_increment(fractional_years, increment, rounding_mode); // aa. Set remainder to fractionalYears - years. remainder = fractional_years - years; @@ -1353,8 +1353,8 @@ ThrowCompletionOr<RoundedDuration> round_duration(GlobalObject& global_object, d // o. Let fractionalMonths be months + days / abs(oneMonthDays). auto fractional_months = months + days / fabs(one_month_days); - // p. Set months to ! RoundNumberToIncrement(fractionalMonths, increment, roundingMode). - months = (double)round_number_to_increment(fractional_months, increment, rounding_mode); + // p. Set months to RoundNumberToIncrement(fractionalMonths, increment, roundingMode). + months = round_number_to_increment(fractional_months, increment, rounding_mode); // q. Set remainder to fractionalMonths - months. remainder = fractional_months - months; @@ -1403,8 +1403,8 @@ ThrowCompletionOr<RoundedDuration> round_duration(GlobalObject& global_object, d // g. Let fractionalWeeks be weeks + days / abs(oneWeekDays). auto fractional_weeks = weeks + days / fabs(one_week_days); - // h. Set weeks to ! RoundNumberToIncrement(fractionalWeeks, increment, roundingMode). - weeks = (double)round_number_to_increment(fractional_weeks, increment, rounding_mode); + // h. Set weeks to RoundNumberToIncrement(fractionalWeeks, increment, roundingMode). + weeks = round_number_to_increment(fractional_weeks, increment, rounding_mode); // i. Set remainder to fractionalWeeks - weeks. remainder = fractional_weeks - weeks; @@ -1417,8 +1417,8 @@ ThrowCompletionOr<RoundedDuration> round_duration(GlobalObject& global_object, d // a. Let fractionalDays be days. auto fractional_days = days; - // b. Set days to ! RoundNumberToIncrement(days, increment, roundingMode). - days = (double)round_number_to_increment(days, increment, rounding_mode); + // b. Set days to RoundNumberToIncrement(days, increment, roundingMode). + days = round_number_to_increment(days, increment, rounding_mode); // c. Set remainder to fractionalDays - days. remainder = fractional_days - days; @@ -1428,8 +1428,8 @@ ThrowCompletionOr<RoundedDuration> round_duration(GlobalObject& global_object, d // a. Let fractionalHours be (fractionalSeconds / 60 + minutes) / 60 + hours. auto fractional_hours = (fractional_seconds / 60 + minutes) / 60 + hours; - // b. Set hours to ! RoundNumberToIncrement(fractionalHours, increment, roundingMode). - hours = (double)round_number_to_increment(fractional_hours, increment, rounding_mode); + // b. Set hours to RoundNumberToIncrement(fractionalHours, increment, roundingMode). + hours = round_number_to_increment(fractional_hours, increment, rounding_mode); // c. Set remainder to fractionalHours - hours. remainder = fractional_hours - hours; @@ -1446,8 +1446,8 @@ ThrowCompletionOr<RoundedDuration> round_duration(GlobalObject& global_object, d // a. Let fractionalMinutes be fractionalSeconds / 60 + minutes. auto fractional_minutes = fractional_seconds / 60 + minutes; - // b. Set minutes to ! RoundNumberToIncrement(fractionalMinutes, increment, roundingMode). - minutes = (double)round_number_to_increment(fractional_minutes, increment, rounding_mode); + // b. Set minutes to RoundNumberToIncrement(fractionalMinutes, increment, roundingMode). + minutes = round_number_to_increment(fractional_minutes, increment, rounding_mode); // c. Set remainder to fractionalMinutes - minutes. remainder = fractional_minutes - minutes; @@ -1460,8 +1460,8 @@ ThrowCompletionOr<RoundedDuration> round_duration(GlobalObject& global_object, d } // 15. Else if unit is "second", then else if (unit == "second"sv) { - // a. Set seconds to ! RoundNumberToIncrement(fractionalSeconds, increment, roundingMode). - seconds = (double)round_number_to_increment(fractional_seconds, increment, rounding_mode); + // a. Set seconds to RoundNumberToIncrement(fractionalSeconds, increment, roundingMode). + seconds = round_number_to_increment(fractional_seconds, increment, rounding_mode); // b. Set remainder to fractionalSeconds - seconds. remainder = fractional_seconds - seconds; @@ -1476,8 +1476,8 @@ ThrowCompletionOr<RoundedDuration> round_duration(GlobalObject& global_object, d // a. Let fractionalMilliseconds be nanoseconds × 10^-6 + microseconds × 10^-3 + milliseconds. auto fractional_milliseconds = nanoseconds * 0.000001 + microseconds * 0.001 + milliseconds; - // b. Set milliseconds to ! RoundNumberToIncrement(fractionalMilliseconds, increment, roundingMode). - milliseconds = (double)round_number_to_increment(fractional_milliseconds, increment, rounding_mode); + // b. Set milliseconds to RoundNumberToIncrement(fractionalMilliseconds, increment, roundingMode). + milliseconds = round_number_to_increment(fractional_milliseconds, increment, rounding_mode); // c. Set remainder to fractionalMilliseconds - milliseconds. remainder = fractional_milliseconds - milliseconds; @@ -1491,8 +1491,8 @@ ThrowCompletionOr<RoundedDuration> round_duration(GlobalObject& global_object, d // a. Let fractionalMicroseconds be nanoseconds × 10^-3 + microseconds. auto fractional_microseconds = nanoseconds * 0.001 + microseconds; - // b. Set microseconds to ! RoundNumberToIncrement(fractionalMicroseconds, increment, roundingMode). - microseconds = (double)round_number_to_increment(fractional_microseconds, increment, rounding_mode); + // b. Set microseconds to RoundNumberToIncrement(fractionalMicroseconds, increment, roundingMode). + microseconds = round_number_to_increment(fractional_microseconds, increment, rounding_mode); // c. Set remainder to fractionalMicroseconds - microseconds. remainder = fractional_microseconds - microseconds; @@ -1508,8 +1508,8 @@ ThrowCompletionOr<RoundedDuration> round_duration(GlobalObject& global_object, d // b. Set remainder to nanoseconds. remainder = nanoseconds; - // c. Set nanoseconds to ! RoundNumberToIncrement(nanoseconds, increment, roundingMode). - nanoseconds = (double)round_number_to_increment(nanoseconds, increment, rounding_mode); + // c. Set nanoseconds to RoundNumberToIncrement(nanoseconds, increment, roundingMode). + nanoseconds = round_number_to_increment(nanoseconds, increment, rounding_mode); // d. Set remainder to remainder - nanoseconds. remainder -= nanoseconds; diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp index 4e3f5a2ba1..008026ea98 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp @@ -196,6 +196,8 @@ BigInt* difference_instant(GlobalObject& global_object, BigInt const& nanosecond // 8.5.8 RoundTemporalInstant ( ns, increment, unit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundtemporalinstant BigInt* round_temporal_instant(GlobalObject& global_object, BigInt const& nanoseconds, u64 increment, StringView unit, StringView rounding_mode) { + auto& vm = global_object.vm(); + // 1. Assert: Type(ns) is BigInt. u64 increment_nanoseconds; @@ -233,8 +235,8 @@ BigInt* round_temporal_instant(GlobalObject& global_object, BigInt const& nanose increment_nanoseconds = increment; } - // 8. Return ! RoundNumberToIncrement(ℝ(ns), incrementNs, roundingMode). - return round_number_to_increment(global_object, nanoseconds, increment_nanoseconds, rounding_mode); + // 8. Return RoundNumberToIncrement(ℝ(ns), incrementNs, roundingMode). + return js_bigint(vm, round_number_to_increment(nanoseconds.big_integer(), increment_nanoseconds, rounding_mode)); } // 8.5.9 TemporalInstantToString ( instant, timeZone, precision ), https://tc39.es/proposal-temporal/#sec-temporal-temporalinstanttostring diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp index c11d31ce0e..0c17238d79 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp @@ -573,7 +573,7 @@ DaysAndTime round_time(u8 hour, u8 minute, u8 second, u16 millisecond, u16 micro quantity = nanosecond; } - // 10. Let result be ! RoundNumberToIncrement(quantity, increment, roundingMode). + // 10. Let result be RoundNumberToIncrement(quantity, increment, roundingMode). auto result = round_number_to_increment(quantity, increment, rounding_mode); // If unit is "day", then diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp index 68564b057f..d4f6c55a8c 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp @@ -382,7 +382,7 @@ String format_iso_time_zone_offset_string(double offset_nanoseconds) // 1. Assert: offsetNanoseconds is an integer. VERIFY(trunc(offset_nanoseconds) == offset_nanoseconds); - // 2. Set offsetNanoseconds to ! RoundNumberToIncrement(offsetNanoseconds, 60 × 10^9, "halfExpand"). + // 2. Set offsetNanoseconds to RoundNumberToIncrement(offsetNanoseconds, 60 × 10^9, "halfExpand"). offset_nanoseconds = round_number_to_increment(offset_nanoseconds, 60000000000, "halfExpand"sv); // 3. If offsetNanoseconds ≥ 0, let sign be "+"; otherwise, let sign be "-". diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp index 4a2148735a..343b6d9d60 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp @@ -89,7 +89,7 @@ ThrowCompletionOr<BigInt const*> interpret_iso_date_time_offset(GlobalObject& gl // c. If matchBehaviour is match minutes, then if (match_behavior == MatchBehavior::MatchMinutes) { - // i. Let roundedCandidateNanoseconds be ! RoundNumberToIncrement(candidateNanoseconds, 60 × 10^9, "halfExpand"). + // i. Let roundedCandidateNanoseconds be RoundNumberToIncrement(candidateNanoseconds, 60 × 10^9, "halfExpand"). auto rounded_candidate_nanoseconds = round_number_to_increment(candidate_nanoseconds, 60000000000, "halfExpand"sv); // ii. If roundedCandidateNanoseconds = offsetNanoseconds, then |