1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 21:07:35 +00:00

LibCrypto+LibJS: Better bigint bitwise_and binop

Bitwise and is defined in terms of two's complement, so some converting
needs to happen for SignedBigInteger's sign/magnitude representation to
work out.

UnsignedBigInteger::bitwise_not() is repurposed to convert all
high-order zero bits to ones up to a limit, for the two's complement
conversion to work.

Fixes test262/test/language/expressions/bitwise-and/bigint.js.
This commit is contained in:
Nico Weber 2022-01-17 19:54:02 -05:00 committed by Ali Mohammad Pur
parent 945d962322
commit 1f98639396
7 changed files with 52 additions and 23 deletions

View file

@ -194,12 +194,36 @@ FLATTEN SignedBigInteger SignedBigInteger::bitwise_or(const SignedBigInteger& ot
FLATTEN SignedBigInteger SignedBigInteger::bitwise_and(const SignedBigInteger& other) const
{
auto result = bitwise_and(other.unsigned_value());
if (!is_negative() && !other.is_negative())
return { unsigned_value().bitwise_and(other.unsigned_value()), false };
// The sign bit will have to be AND'd manually.
result.m_sign = is_negative() && other.is_negative();
// These two just use that -x == ~x + 1 (see below).
return result;
// -A & B == (~A + 1) & B.
if (is_negative() && !other.is_negative())
return { unsigned_value().bitwise_not_fill_to_size(other.trimmed_length()).plus(1).bitwise_and(other.unsigned_value()), false };
// A & -B == A & (~B + 1).
if (!is_negative() && other.is_negative())
return { unsigned_value().bitwise_and(other.unsigned_value().bitwise_not_fill_to_size(trimmed_length()).plus(1)), false };
// Both numbers are negative.
// x + ~x == 0xff...ff, up to however many bits x is wide.
// In two's complement, x + ~x + 1 == 0 since the 1 in the overflowing bit position is masked out.
// Rearranging terms, ~x = -x - 1 (eq1).
// Substituting x = y - 1, ~(y - 1) == -(y - 1) - 1 == -y +1 -1 == -y, or ~(y - 1) == -y (eq2).
// Since both numbers are negative, we want to compute -A & -B.
// Per (eq2):
// -A & -B == ~(A - 1) & ~(B - 1)
// Inverting both sides:
// ~(-A & -B) == ~(~(A - 1) & ~(B - 1)) == ~~(A - 1) | ~~(B - 1) == (A - 1) | (B - 1).
// Applying (q1) on the LHS:
// -(-A & -B) - 1 == (A - 1) | (B - 1)
// Adding 1 on both sides and then multiplying both sides by -1:
// -A & -B == -( (A - 1) | (B - 1) + 1)
// So we can compute the bitwise and by returning a negative number with magnitude (A - 1) | (B - 1) + 1.
// This is better than the naive (~A + 1) & (~B + 1) because it needs just one O(n) scan for the or instead of 2 for the ~s.
return { unsigned_value().minus(1).bitwise_or(other.unsigned_value().minus(1)).plus(1), true };
}
FLATTEN SignedBigInteger SignedBigInteger::bitwise_xor(const SignedBigInteger& other) const