From c7e4a99219d7184af9d056c878a586d1e5d69fce Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Thu, 13 Nov 2025 10:21:26 -0500 Subject: [PATCH] LibJS: Handle negativity in Temporal's ApplyUnsignedRoundingMode I don't fully understand the BigInt math here, as the computation for d1 and d2 don't align with the spec due to BigInt logic. This was discussed a bit in SerenityOS's Discord some years ago: https://discord.com/channels/830522505605283862/851522357734408232/978786665306918932 But some new tests in test262 indicate that we need to handle negative values here, instead of just throwing away the sign. --- .../Runtime/Temporal/AbstractOperations.cpp | 5 +++-- .../Instant/Instant.prototype.toString.js | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index b9255acde20..85cecf1e7c5 100644 --- a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -947,10 +947,11 @@ Crypto::SignedBigInteger apply_unsigned_rounding_mode(Crypto::SignedDivisionResu return r2; // 6. Let d1 be x – r1. - auto d1 = x.remainder.unsigned_value(); + auto const& d1 = x.remainder; // 7. Let d2 be r2 – x. - auto d2 = MUST(increment.minus(x.remainder.unsigned_value())); + auto d2 = x.remainder.is_negative() ? x.remainder.plus(increment) : x.remainder.minus(increment); + d2.negate(); // 8. If d1 < d2, return r1. if (d1 < d2) diff --git a/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.toString.js b/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.toString.js index 8997d34227d..3dcad25a8d1 100644 --- a/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.toString.js +++ b/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.toString.js @@ -52,6 +52,22 @@ describe("correct behavior", () => { expect(instant.toString(options)).toBe(expected); } }); + + test("rounding", () => { + const instant = new Temporal.Instant(-999999999999999990n); + const roundedDown = "1938-04-24T22:13:20.000Z"; + const roundedUp = "1938-04-24T22:13:20.001Z"; + + for (const roundingMode of ["halfCeil", "halfFloor", "halfExpand", "halfTrunc", "halfEven", "floor", "trunc"]) { + const options = { smallestUnit: "millisecond", roundingMode }; + expect(instant.toString(options)).toBe(roundedDown); + } + + for (const roundingMode of ["ceil", "expand"]) { + const options = { smallestUnit: "millisecond", roundingMode }; + expect(instant.toString(options)).toBe(roundedUp); + } + }); }); describe("errors", () => {