From e663504df13029bf1e6656bcb3ae5074d978694a Mon Sep 17 00:00:00 2001 From: davidot Date: Sat, 20 Aug 2022 00:23:11 +0200 Subject: [PATCH] LibJS: Fix that leftshift for BigInts did not round down For negative number this previously rounded towards zero instead of the intended always rounding down. --- Userland/Libraries/LibJS/Runtime/Value.cpp | 21 +++++++++++++++---- .../operators/binary-bitwise-left-shift.js | 10 +++++++++ .../operators/binary-bitwise-right-shift.js | 10 +++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/Value.cpp b/Userland/Libraries/LibJS/Runtime/Value.cpp index 7e343c2618..cb5650540f 100644 --- a/Userland/Libraries/LibJS/Runtime/Value.cpp +++ b/Userland/Libraries/LibJS/Runtime/Value.cpp @@ -1028,11 +1028,24 @@ ThrowCompletionOr left_shift(VM& vm, Value lhs, Value rhs) return Value(lhs_i32 << rhs_u32); } if (both_bigint(lhs_numeric, rhs_numeric)) { + // 6.1.6.2.9 BigInt::leftShift ( x, y ), https://tc39.es/ecma262/#sec-numeric-types-bigint-leftShift auto multiplier_divisor = Crypto::SignedBigInteger { Crypto::NumberTheory::Power(Crypto::UnsignedBigInteger(2), rhs_numeric.as_bigint().big_integer().unsigned_value()) }; - if (rhs_numeric.as_bigint().big_integer().is_negative()) - return Value(js_bigint(vm, lhs_numeric.as_bigint().big_integer().divided_by(multiplier_divisor).quotient)); - else - return Value(js_bigint(vm, lhs_numeric.as_bigint().big_integer().multiplied_by(multiplier_divisor))); + + // 1. If y < 0ℤ, then + if (rhs_numeric.as_bigint().big_integer().is_negative()) { + // a. Return the BigInt value that represents ℝ(x) / 2^-y, rounding down to the nearest integer, including for negative numbers. + // NOTE: Since y is negative we can just do ℝ(x) / 2^|y| + auto const& big_integer = lhs_numeric.as_bigint().big_integer(); + auto division_result = big_integer.divided_by(multiplier_divisor); + + // For positive initial values and no remainder just return quotient + if (division_result.remainder.is_zero() || !big_integer.is_negative()) + return js_bigint(vm, division_result.quotient); + // For negative round "down" to the next negative number + return js_bigint(vm, division_result.quotient.minus(Crypto::SignedBigInteger { 1 })); + } + // 2. Return the BigInt value that represents ℝ(x) × 2^y. + return Value(js_bigint(vm, lhs_numeric.as_bigint().big_integer().multiplied_by(multiplier_divisor))); } return vm.throw_completion(ErrorType::BigIntBadOperatorOtherType, "left-shift"); } diff --git a/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-left-shift.js b/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-left-shift.js index 07c5c9028e..68122604c6 100644 --- a/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-left-shift.js +++ b/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-left-shift.js @@ -63,3 +63,13 @@ test("shifting with non-numeric values", () => { expect(Infinity << Infinity).toBe(0); expect(-Infinity << Infinity).toBe(0); }); + +describe("logical left shift on big ints", () => { + expect(3n << -1n).toBe(1n); + expect(3n << -2n).toBe(0n); + expect(-3n << -1n).toBe(-2n); + expect(-3n << -2n).toBe(-1n); + expect(-3n << -128n).toBe(-1n); + expect(3n << 6n).toBe(192n); + expect(3n << 0n).toBe(3n); +}); diff --git a/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-right-shift.js b/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-right-shift.js index efdc4b79f1..925e6d1fb7 100644 --- a/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-right-shift.js +++ b/Userland/Libraries/LibJS/Tests/operators/binary-bitwise-right-shift.js @@ -64,3 +64,13 @@ test("shifting with non-numeric values", () => { expect(Infinity >> Infinity).toBe(0); expect(-Infinity >> Infinity).toBe(0); }); + +describe("logical right shift on big ints", () => { + expect(3n >> 1n).toBe(1n); + expect(3n >> 2n).toBe(0n); + expect(-3n >> 1n).toBe(-2n); + expect(-3n >> 2n).toBe(-1n); + expect(-3n >> 128n).toBe(-1n); + expect(-3n >> -6n).toBe(-192n); + expect(-3n >> 0n).toBe(-3n); +});