Skip to content

Commit 26e895e

Browse files
TinusgragLinandrewrk
authored andcommitted
math.big.int: fix incorrect bitAnd behavior
1 parent f8f43ca commit 26e895e

File tree

2 files changed

+36
-16
lines changed

2 files changed

+36
-16
lines changed

lib/std/math/big/int.zig

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,7 +1236,9 @@ pub const Mutable = struct {
12361236
/// r may alias with a or b.
12371237
///
12381238
/// Asserts that r has enough limbs to store the result.
1239-
/// If a or b is positive, the upper bound is `@min(a.limbs.len, b.limbs.len)`.
1239+
/// If only a is positive, the upper bound is `a.limbs.len`.
1240+
/// If only b is positive, the upper bound is `b.limbs.len`.
1241+
/// If a and b are positive, the upper bound is `@min(a.limbs.len, b.limbs.len)`.
12401242
/// If a and b are negative, the upper bound is `@max(a.limbs.len, b.limbs.len) + 1`.
12411243
pub fn bitAnd(r: *Mutable, a: Const, b: Const) void {
12421244
// Trivial cases, llsignedand does not support zero.
@@ -1250,10 +1252,10 @@ pub const Mutable = struct {
12501252

12511253
if (a.limbs.len >= b.limbs.len) {
12521254
r.positive = llsignedand(r.limbs, a.limbs, a.positive, b.limbs, b.positive);
1253-
r.normalize(if (a.positive or b.positive) b.limbs.len else a.limbs.len + 1);
1255+
r.normalize(if (b.positive) b.limbs.len else if (a.positive) a.limbs.len else a.limbs.len + 1);
12541256
} else {
12551257
r.positive = llsignedand(r.limbs, b.limbs, b.positive, a.limbs, a.positive);
1256-
r.normalize(if (a.positive or b.positive) a.limbs.len else b.limbs.len + 1);
1258+
r.normalize(if (a.positive) a.limbs.len else if (b.positive) b.limbs.len else b.limbs.len + 1);
12571259
}
12581260
}
12591261

@@ -3136,10 +3138,10 @@ pub const Managed = struct {
31363138

31373139
/// r = a & b
31383140
pub fn bitAnd(r: *Managed, a: *const Managed, b: *const Managed) !void {
3139-
const cap = if (a.isPositive() or b.isPositive())
3140-
@min(a.len(), b.len())
3141-
else
3142-
@max(a.len(), b.len()) + 1;
3141+
const cap = if (a.len() >= b.len())
3142+
if (b.isPositive()) b.len() else if (a.isPositive()) a.len() else a.len() + 1
3143+
else if (a.isPositive()) a.len() else if (b.isPositive()) b.len() else b.len() + 1;
3144+
31433145
try r.ensureCapacity(cap);
31443146
var m = r.toMutable();
31453147
m.bitAnd(a.toConst(), b.toConst());
@@ -3885,7 +3887,7 @@ fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_p
38853887
// x & ~a can only clear bits, so (x & ~a) <= x, meaning (-b - 1) + 1 never overflows.
38863888
assert(r_carry == 0);
38873889

3888-
// With b = 0 and b_borrow = 0, we get ~a & (-0 - 0) = ~a & 0 = 0.
3890+
// With b = 0 and b_borrow = 0, we get ~a & (0 - 0) = ~a & 0 = 0.
38893891
// Omit setting the upper bytes, just deal with those when calling llsignedor.
38903892

38913893
return false;
@@ -3922,7 +3924,7 @@ fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_p
39223924
// for x = a - 1 and y = b - 1, the +1 term would never cause an overflow.
39233925
assert(r_carry == 0);
39243926

3925-
// With b = 0 and b_borrow = 0 we get (-a - 1) & (-0 - 0) = (-a - 1) & 0 = 0.
3927+
// With b = 0 and b_borrow = 0 we get (-a - 1) & (0 - 0) = (-a - 1) & 0 = 0.
39263928
// Omit setting the upper bytes, just deal with those when calling llsignedor.
39273929
return false;
39283930
}
@@ -3932,13 +3934,15 @@ fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_p
39323934
// r may alias.
39333935
// a and b must not be 0.
39343936
// Returns `true` when the result is positive.
3935-
// When either or both of a and b are positive, r requires at least `b.len` limbs of storage.
3936-
// When both a and b are negative, r requires at least `a.limbs.len + 1` limbs of storage.
3937+
// We assume `a.len >= b.len` here, so:
3938+
// 1. when b is positive, r requires at least `b.len` limbs of storage,
3939+
// 2. when b is negative but a is positive, r requires at least `a.len` limbs of storage,
3940+
// 3. when both a and b are negative, r requires at least `a.len + 1` limbs of storage.
39373941
fn llsignedand(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_positive: bool) bool {
39383942
@setRuntimeSafety(debug_safety);
39393943
assert(a.len != 0 and b.len != 0);
39403944
assert(a.len >= b.len);
3941-
assert(r.len >= if (!a_positive and !b_positive) a.len + 1 else b.len);
3945+
assert(r.len >= if (b_positive) b.len else if (a_positive) a.len else a.len + 1);
39423946

39433947
if (a_positive and b_positive) {
39443948
// Trivial case, result is positive.
@@ -3987,9 +3991,12 @@ fn llsignedand(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_
39873991

39883992
assert(b_borrow == 0); // b was 0
39893993

3990-
// With b = 0 and b_borrow = 0 we have a & ~(-0 - 0) = a & 0 = 0, so
3991-
// the upper bytes are zero. Omit setting them here and simply discard
3992-
// them whenever llsignedand is called.
3994+
// With b = 0 and b_borrow = 0 we have a & ~(0 - 0) = a & ~0 = a, so
3995+
// the upper bytes are the same as those of a.
3996+
3997+
while (i < a.len) : (i += 1) {
3998+
r[i] = a[i];
3999+
}
39934000

39944001
return true;
39954002
} else {
@@ -4017,7 +4024,7 @@ fn llsignedand(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_
40174024
// b is at least 1, so this should never underflow.
40184025
assert(b_borrow == 0); // b was 0
40194026

4020-
// With b = 0 and b_borrow = 0 we get (-a - 1) | (-0 - 0) = (-a - 1) | 0 = -a - 1.
4027+
// With b = 0 and b_borrow = 0 we get (-a - 1) | (0 - 0) = (-a - 1) | 0 = -a - 1.
40214028
while (i < a.len) : (i += 1) {
40224029
const ov1 = @subWithOverflow(a[i], a_borrow);
40234030
a_borrow = ov1[1];

lib/std/math/big/int_test.zig

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1487,6 +1487,19 @@ test "bitAnd #10932" {
14871487
try testing.expect((try res.to(i32)) == 0);
14881488
}
14891489

1490+
test "bit And #19235" {
1491+
var a = try Managed.initSet(testing.allocator, -0xffffffffffffffff);
1492+
defer a.deinit();
1493+
var b = try Managed.initSet(testing.allocator, 0x10000000000000000);
1494+
defer b.deinit();
1495+
var r = try Managed.init(testing.allocator);
1496+
defer r.deinit();
1497+
1498+
try r.bitAnd(&a, &b);
1499+
1500+
try testing.expect((try r.to(i128)) == 0x10000000000000000);
1501+
}
1502+
14901503
test "div floor single-single +/+" {
14911504
const u: i32 = 5;
14921505
const v: i32 = 3;

0 commit comments

Comments
 (0)