authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-09-22 03:27:56+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-09-22 02:21:55-04:00
log4afe4bdfe7dda638b35a9d388aac9a53d18cc4ba
tree6b016ce7c5e30d2ace0a6519aef08eca224ab910
parentaecebf38acc8835db21eeea7b53e4ee26ec739a8

big ints: 2s complement signed xor


2 files changed, 132 insertions(+), 11 deletions(-)

lib/std/math/big/int.zig+65-11
......@@ -584,19 +584,29 @@ pub const Mutable = struct {
584584 r.positive = a.positive and b.positive;
585585 }
586586
587 /// r = a ^ b
587 /// r = a ^ b under 2s complement semantics.
588588 /// r may alias with a or b.
589589 ///
590 /// Asserts that r has enough limbs to store the result. Upper bound is `math.max(a.limbs.len, b.limbs.len)`.
590 /// Asserts that r has enough limbs to store the result. If a and b share the same signedness, the
591 /// upper bound is `math.max(a.limbs.len, b.limbs.len)`. Otherwise, if either a or b is negative
592 /// but not both, the upper bound is `math.max(a.limbs.len, b.limbs.len) + 1`.
591593 pub fn bitXor(r: *Mutable, a: Const, b: Const) void {
594 // Trivial cases, because llsignedxor does not support negative zero.
595 if (a.eqZero()) {
596 r.copy(b);
597 return;
598 } else if (b.eqZero()) {
599 r.copy(a);
600 return;
601 }
602
592603 if (a.limbs.len > b.limbs.len) {
593 llxor(r.limbs[0..], a.limbs[0..a.limbs.len], b.limbs[0..b.limbs.len]);
594 r.normalize(a.limbs.len);
604 r.positive = llsignedxor(r.limbs, a.limbs, a.positive, b.limbs, b.positive);
605 r.normalize(a.limbs.len + @boolToInt(a.positive != b.positive));
595606 } else {
596 llxor(r.limbs[0..], b.limbs[0..b.limbs.len], a.limbs[0..a.limbs.len]);
597 r.normalize(b.limbs.len);
607 r.positive = llsignedxor(r.limbs, b.limbs, b.positive, a.limbs, a.positive);
608 r.normalize(b.limbs.len + @boolToInt(a.positive != b.positive));
598609 }
599 r.positive = a.positive or b.positive;
600610 }
601611
602612 /// rma may alias x or y.
......@@ -1845,7 +1855,9 @@ pub const Managed = struct {
18451855
18461856 /// r = a ^ b
18471857 pub fn bitXor(r: *Managed, a: Managed, b: Managed) !void {
1848 try r.ensureCapacity(math.max(a.len(), b.len()));
1858 var cap = math.max(a.len(), b.len()) + @boolToInt(a.isPositive() != b.isPositive());
1859 try r.ensureCapacity(cap);
1860
18491861 var m = r.toMutable();
18501862 m.bitXor(a.toConst(), b.toConst());
18511863 r.setMetadata(m.positive, m.len);
......@@ -2249,17 +2261,59 @@ fn lland(r: []Limb, a: []const Limb, b: []const Limb) void {
22492261 }
22502262}
22512263
2252fn llxor(r: []Limb, a: []const Limb, b: []const Limb) void {
2264// r = a ^ b with 2s complement semantics.
2265// r may alias.
2266// a and b must not be -0.
2267// Returns `true` when the result is positive.
2268fn llsignedxor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_positive: bool) bool {
2269 @setRuntimeSafety(debug_safety);
2270 assert(a.len != 0 and b.len != 0);
22532271 assert(r.len >= a.len);
22542272 assert(a.len >= b.len);
22552273
2274 // If a and b are positive, the result is positive and r = a ^ b.
2275 // If a negative, b positive, result is negative and we have
2276 // r = --(--a ^ b)
2277 // = --(~(-a - 1) ^ b)
2278 // = -(~(~(-a - 1) ^ b) + 1)
2279 // = -(((-a - 1) ^ b) + 1)
2280 // Same if a is positive and b is negative, sides switched.
2281 // If both a and b are negative, the result is positive and we have
2282 // r = (--a) ^ (--b)
2283 // = ~(-a - 1) ^ ~(-b - 1)
2284 // = (-a - 1) ^ (-b - 1)
2285 // These operations can be made more generic as follows:
2286 // - If a is negative, subtract 1 from |a| before the xor.
2287 // - If b is negative, subtract 1 from |b| before the xor.
2288 // - if the result is supposed to be negative, add 1.
2289
22562290 var i: usize = 0;
2291 var a_borrow = @boolToInt(!a_positive);
2292 var b_borrow = @boolToInt(!b_positive);
2293 var r_carry = @boolToInt(a_positive != b_positive);
2294
22572295 while (i < b.len) : (i += 1) {
2258 r[i] = a[i] ^ b[i];
2296 var a_limb: Limb = undefined;
2297 a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &a_limb));
2298
2299 var b_limb: Limb = undefined;
2300 b_borrow = @boolToInt(@subWithOverflow(Limb, b[i], b_borrow, &b_limb));
2301
2302 r[i] = a_limb ^ b_limb;
2303 r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i]));
22592304 }
2305
22602306 while (i < a.len) : (i += 1) {
2261 r[i] = a[i];
2307 a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &r[i]));
2308 r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i]));
22622309 }
2310
2311 r[i] = r_carry;
2312
2313 assert(a_borrow == 0);
2314 assert(b_borrow == 0);
2315
2316 return a_positive == b_positive;
22632317}
22642318
22652319/// r MUST NOT alias x.
lib/std/math/big/int_test.zig+67
......@@ -5,6 +5,7 @@ const Managed = std.math.big.int.Managed;
55const Mutable = std.math.big.int.Mutable;
66const Limb = std.math.big.Limb;
77const DoubleLimb = std.math.big.DoubleLimb;
8const SignedDoubleLimb = std.math.big.SignedDoubleLimb;
89const maxInt = std.math.maxInt;
910const minInt = std.math.minInt;
1011
......@@ -1386,6 +1387,72 @@ test "big.int bitwise xor multi-limb" {
13861387 try testing.expect((try a.to(DoubleLimb)) == (maxInt(Limb) + 1) ^ maxInt(Limb));
13871388}
13881389
1390test "big.int bitwise xor single negative simple" {
1391 var a = try Managed.initSet(testing.allocator, 0x6b03e381328a3154);
1392 defer a.deinit();
1393 var b = try Managed.initSet(testing.allocator, -0x45fd3acef9191fad);
1394 defer b.deinit();
1395
1396 try a.bitXor(a, b);
1397
1398 try testing.expect((try a.to(i64)) == -0x2efed94fcb932ef9);
1399}
1400
1401test "big.int bitwise xor single negative zero" {
1402 var a = try Managed.initSet(testing.allocator, 0);
1403 defer a.deinit();
1404 var b = try Managed.initSet(testing.allocator, -0);
1405 defer b.deinit();
1406
1407 try a.bitXor(a, b);
1408
1409 try testing.expect(a.eqZero());
1410}
1411
1412test "big.int bitwise xor single negative multi-limb" {
1413 var a = try Managed.initSet(testing.allocator, -0x9849c6e7a10d66d0e4260d4846254c32);
1414 defer a.deinit();
1415 var b = try Managed.initSet(testing.allocator, 0xf2194e7d1c855272a997fcde16f6d5a8);
1416 defer b.deinit();
1417
1418 try a.bitXor(a, b);
1419
1420 try testing.expect((try a.to(i128)) == -0x6a50889abd8834a24db1f19650d3999a);
1421}
1422
1423test "big.int bitwise xor single negative overflow" {
1424 var a = try Managed.initSet(testing.allocator, maxInt(Limb));
1425 defer a.deinit();
1426 var b = try Managed.initSet(testing.allocator, -1);
1427 defer b.deinit();
1428
1429 try a.bitXor(a, b);
1430
1431 try testing.expect((try a.to(SignedDoubleLimb)) == -(maxInt(Limb) + 1));
1432}
1433
1434test "big.int bitwise xor double negative simple" {
1435 var a = try Managed.initSet(testing.allocator, -0x8e48bd5f755ef1f3);
1436 defer a.deinit();
1437 var b = try Managed.initSet(testing.allocator, -0x4dd4fa576f3046ac);
1438 defer b.deinit();
1439
1440 try a.bitXor(a, b);
1441
1442 try testing.expect((try a.to(u64)) == 0xc39c47081a6eb759);
1443}
1444
1445test "big.int bitwise xor double negative multi-limb" {
1446 var a = try Managed.initSet(testing.allocator, -0x684e5da8f500ec8ca7204c33ccc51c9c);
1447 defer a.deinit();
1448 var b = try Managed.initSet(testing.allocator, -0xcb07736a7b62289c78d967c3985eebeb);
1449 defer b.deinit();
1450
1451 try a.bitXor(a, b);
1452
1453 try testing.expect((try a.to(u128)) == 0xa3492ec28e62c410dff92bf0549bf771);
1454}
1455
13891456test "big.int bitwise or simple" {
13901457 var a = try Managed.initSet(testing.allocator, 0xffffffff11111111);
13911458 defer a.deinit();