authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-09-23 13:17:32-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-09-23 13:17:32-04:00
logdd81a2147da7b8a216012be2c483c71c4a65cf8b
treefbec7c0e39f5341e5f928f3d9e0b243b8df3d4e9
parenta0a847f2e40046387e2e2b8a0b8dae9cb27ca22a
parentcd3dcc225b52be981b52b71dcdb34ba176f4081b
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #9825 from Snektron/big-int-signed-and-or

big ints: 2s complement signed and/or

2 files changed, 409 insertions(+), 29 deletions(-)

lib/std/math/big/int.zig+266-29
......@@ -552,36 +552,54 @@ pub const Mutable = struct {
552552 r.positive = a.positive;
553553 }
554554
555 /// r = a | b
555 /// r = a | b under 2s complement semantics.
556556 /// r may alias with a or b.
557557 ///
558558 /// a and b are zero-extended to the longer of a or b.
559559 ///
560560 /// Asserts that r has enough limbs to store the result. Upper bound is `math.max(a.limbs.len, b.limbs.len)`.
561561 pub fn bitOr(r: *Mutable, a: Const, b: Const) void {
562 if (a.limbs.len > b.limbs.len) {
563 llor(r.limbs[0..], a.limbs[0..a.limbs.len], b.limbs[0..b.limbs.len]);
564 r.len = a.limbs.len;
562 // Trivial cases, llsignedor does not support zero.
563 if (a.eqZero()) {
564 r.copy(b);
565 return;
566 } else if (b.eqZero()) {
567 r.copy(a);
568 return;
569 }
570
571 if (a.limbs.len >= b.limbs.len) {
572 r.positive = llsignedor(r.limbs, a.limbs, a.positive, b.limbs, b.positive);
573 r.normalize(if (b.positive) a.limbs.len else b.limbs.len);
565574 } else {
566 llor(r.limbs[0..], b.limbs[0..b.limbs.len], a.limbs[0..a.limbs.len]);
567 r.len = b.limbs.len;
575 r.positive = llsignedor(r.limbs, b.limbs, b.positive, a.limbs, a.positive);
576 r.normalize(if (a.positive) b.limbs.len else a.limbs.len);
568577 }
569 r.positive = a.positive or b.positive;
570578 }
571579
572 /// r = a & b
580 /// r = a & b under 2s complement semantics.
573581 /// r may alias with a or b.
574582 ///
575 /// Asserts that r has enough limbs to store the result. Upper bound is `math.min(a.limbs.len, b.limbs.len)`.
583 /// Asserts that r has enough limbs to store the result.
584 /// If a or b is positive, the upper bound is `math.min(a.limbs.len, b.limbs.len)`.
585 /// If a and b are negative, the upper bound is `math.max(a.limbs.len, b.limbs.len) + 1`.
576586 pub fn bitAnd(r: *Mutable, a: Const, b: Const) void {
577 if (a.limbs.len > b.limbs.len) {
578 lland(r.limbs[0..], a.limbs[0..a.limbs.len], b.limbs[0..b.limbs.len]);
579 r.normalize(b.limbs.len);
587 // Trivial cases, llsignedand does not support zero.
588 if (a.eqZero()) {
589 r.copy(a);
590 return;
591 } else if (b.eqZero()) {
592 r.copy(b);
593 return;
594 }
595
596 if (a.limbs.len >= b.limbs.len) {
597 r.positive = llsignedand(r.limbs, a.limbs, a.positive, b.limbs, b.positive);
598 r.normalize(if (a.positive or b.positive) b.limbs.len else a.limbs.len + 1);
580599 } else {
581 lland(r.limbs[0..], b.limbs[0..b.limbs.len], a.limbs[0..a.limbs.len]);
582 r.normalize(a.limbs.len);
600 r.positive = llsignedand(r.limbs, b.limbs, b.positive, a.limbs, a.positive);
601 r.normalize(if (a.positive or b.positive) a.limbs.len else b.limbs.len + 1);
583602 }
584 r.positive = a.positive and b.positive;
585603 }
586604
587605 /// r = a ^ b under 2s complement semantics.
......@@ -1847,7 +1865,11 @@ pub const Managed = struct {
18471865
18481866 /// r = a & b
18491867 pub fn bitAnd(r: *Managed, a: Managed, b: Managed) !void {
1850 try r.ensureCapacity(math.min(a.len(), b.len()));
1868 const cap = if (a.isPositive() or b.isPositive())
1869 math.min(a.len(), b.len())
1870 else
1871 math.max(a.len(), b.len()) + 1;
1872 try r.ensureCapacity(cap);
18511873 var m = r.toMutable();
18521874 m.bitAnd(a.toConst(), b.toConst());
18531875 r.setMetadata(m.positive, m.len);
......@@ -2236,28 +2258,236 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) void {
22362258 }
22372259}
22382260
2239fn llor(r: []Limb, a: []const Limb, b: []const Limb) void {
2261// r = a | b with 2s complement semantics.
2262// r may alias.
2263// a and b must not be 0.
2264// Returns `true` when the result is positive.
2265// When b is positive, r requires at least `a.len` limbs of storage.
2266// When b is negative, r requires at least `b.len` limbs of storage.
2267fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_positive: bool) bool {
22402268 @setRuntimeSafety(debug_safety);
22412269 assert(r.len >= a.len);
22422270 assert(a.len >= b.len);
22432271
2244 var i: usize = 0;
2245 while (i < b.len) : (i += 1) {
2246 r[i] = a[i] | b[i];
2247 }
2248 while (i < a.len) : (i += 1) {
2249 r[i] = a[i];
2272 if (a_positive and b_positive) {
2273 // Trivial case, result is positive.
2274 var i: usize = 0;
2275 while (i < b.len) : (i += 1) {
2276 r[i] = a[i] | b[i];
2277 }
2278 while (i < a.len) : (i += 1) {
2279 r[i] = a[i];
2280 }
2281
2282 return true;
2283 } else if (!a_positive and b_positive) {
2284 // Result is negative.
2285 // r = (--a) | b
2286 // = ~(-a - 1) | b
2287 // = ~(-a - 1) | ~~b
2288 // = ~((-a - 1) & ~b)
2289 // = -(((-a - 1) & ~b) + 1)
2290
2291 var i: usize = 0;
2292 var a_borrow: u1 = 1;
2293 var r_carry: u1 = 1;
2294
2295 while (i < b.len) : (i += 1) {
2296 var a_limb: Limb = undefined;
2297 a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &a_limb));
2298
2299 r[i] = a_limb & ~b[i];
2300 r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i]));
2301 }
2302
2303 // In order for r_carry to be nonzero at this point, ~b[i] would need to be
2304 // all ones, which would require b[i] to be zero. This cannot be when
2305 // b is normalized, so there cannot be a carry here.
2306 // Also, x & ~b can only clear bits, so (x & ~b) <= x, meaning (-a - 1) + 1 never overflows.
2307 assert(r_carry == 0);
2308
2309 // With b = 0, we get (-a - 1) & ~0 = -a - 1.
2310 // Note, if a_borrow is zero we do not need to compute anything for
2311 // the higher limbs so we can early return here.
2312 while (i < a.len and a_borrow == 1) : (i += 1) {
2313 a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &r[i]));
2314 }
2315
2316 assert(a_borrow == 0); // a was 0.
2317
2318 return false;
2319 } else if (a_positive and !b_positive) {
2320 // Result is negative.
2321 // r = a | (--b)
2322 // = a | ~(-b - 1)
2323 // = ~~a | ~(-b - 1)
2324 // = ~(~a & (-b - 1))
2325 // = -((~a & (-b - 1)) + 1)
2326
2327 var i: usize = 0;
2328 var b_borrow: u1 = 1;
2329 var r_carry: u1 = 1;
2330
2331 while (i < b.len) : (i += 1) {
2332 var b_limb: Limb = undefined;
2333 b_borrow = @boolToInt(@subWithOverflow(Limb, b[i], b_borrow, &b_limb));
2334
2335 r[i] = ~a[i] & b_limb;
2336 r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i]));
2337 }
2338
2339 // b is at least 1, so this should never underflow.
2340 assert(b_borrow == 0); // b was 0
2341
2342 // x & ~a can only clear bits, so (x & ~a) <= x, meaning (-b - 1) + 1 never overflows.
2343 assert(r_carry == 0);
2344
2345 // With b = 0 and b_borrow = 0, we get ~a & (-0 - 0) = ~a & 0 = 0.
2346 // Omit setting the upper bytes, just deal with those when calling llsignedor.
2347
2348 return false;
2349 } else {
2350 // Result is negative.
2351 // r = (--a) | (--b)
2352 // = ~(-a - 1) | ~(-b - 1)
2353 // = ~((-a - 1) & (-b - 1))
2354 // = -(~(~((-a - 1) & (-b - 1))) + 1)
2355 // = -((-a - 1) & (-b - 1) + 1)
2356
2357 var i: usize = 0;
2358 var a_borrow: u1 = 1;
2359 var b_borrow: u1 = 1;
2360 var r_carry: u1 = 1;
2361
2362 while (i < b.len) : (i += 1) {
2363 var a_limb: Limb = undefined;
2364 a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &a_limb));
2365
2366 var b_limb: Limb = undefined;
2367 b_borrow = @boolToInt(@subWithOverflow(Limb, b[i], b_borrow, &b_limb));
2368
2369 r[i] = a_limb & b_limb;
2370 r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i]));
2371 }
2372
2373 // b is at least 1, so this should never underflow.
2374 assert(b_borrow == 0); // b was 0
2375
2376 // Can never overflow because in order for b_limb to be maxInt(Limb),
2377 // b_borrow would need to equal 1.
2378
2379 // x & y can only clear bits, meaning x & y <= x and x & y <= y. This implies that
2380 // for x = a - 1 and y = b - 1, the +1 term would never cause an overflow.
2381 assert(r_carry == 0);
2382
2383 // With b = 0 and b_borrow = 0 we get (-a - 1) & (-0 - 0) = (-a - 1) & 0 = 0.
2384 // Omit setting the upper bytes, just deal with those when calling llsignedor.
2385 return false;
22502386 }
22512387}
22522388
2253fn lland(r: []Limb, a: []const Limb, b: []const Limb) void {
2389// r = a & b with 2s complement semantics.
2390// r may alias.
2391// a and b must not be 0.
2392// Returns `true` when the result is positive.
2393// When either or both of a and b are positive, r requires at least `b.len` limbs of storage.
2394// When both a and b are negative, r requires at least `a.limbs.len + 1` limbs of storage.
2395fn llsignedand(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_positive: bool) bool {
22542396 @setRuntimeSafety(debug_safety);
2255 assert(r.len >= b.len);
2397 assert(a.len != 0 and b.len != 0);
2398 assert(r.len >= a.len);
22562399 assert(a.len >= b.len);
22572400
2258 var i: usize = 0;
2259 while (i < b.len) : (i += 1) {
2260 r[i] = a[i] & b[i];
2401 if (a_positive and b_positive) {
2402 // Trivial case, result is positive.
2403 var i: usize = 0;
2404 while (i < b.len) : (i += 1) {
2405 r[i] = a[i] & b[i];
2406 }
2407
2408 // With b = 0 we have a & 0 = 0, so the upper bytes are zero.
2409 // Omit setting them here and simply discard them whenever
2410 // llsignedand is called.
2411
2412 return true;
2413 } else if (!a_positive and b_positive) {
2414 // Result is positive.
2415 // r = (--a) & b
2416 // = ~(-a - 1) & b
2417
2418 var i: usize = 0;
2419 var a_borrow: u1 = 1;
2420
2421 while (i < b.len) : (i += 1) {
2422 var a_limb: Limb = undefined;
2423 a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &a_limb));
2424 r[i] = ~a_limb & b[i];
2425 }
2426
2427 // With b = 0 we have ~(a - 1) & 0 = 0, so the upper bytes are zero.
2428 // Omit setting them here and simply discard them whenever
2429 // llsignedand is called.
2430
2431 return true;
2432 } else if (a_positive and !b_positive) {
2433 // Result is positive.
2434 // r = a & (--b)
2435 // = a & ~(-b - 1)
2436
2437 var i: usize = 0;
2438 var b_borrow: u1 = 1;
2439
2440 while (i < b.len) : (i += 1) {
2441 var a_limb: Limb = undefined;
2442 b_borrow = @boolToInt(@subWithOverflow(Limb, b[i], b_borrow, &a_limb));
2443 r[i] = a[i] & ~a_limb;
2444 }
2445
2446 assert(b_borrow == 0); // b was 0
2447
2448 // With b = 0 and b_borrow = 0 we have a & ~(-0 - 0) = a & 0 = 0, so
2449 // the upper bytes are zero. Omit setting them here and simply discard
2450 // them whenever llsignedand is called.
2451
2452 return true;
2453 } else {
2454 // Result is negative.
2455 // r = (--a) & (--b)
2456 // = ~(-a - 1) & ~(-b - 1)
2457 // = ~((-a - 1) | (-b - 1))
2458 // = -(((-a - 1) | (-b - 1)) + 1)
2459
2460 var i: usize = 0;
2461 var a_borrow: u1 = 1;
2462 var b_borrow: u1 = 1;
2463 var r_carry: u1 = 1;
2464
2465 while (i < b.len) : (i += 1) {
2466 var a_limb: Limb = undefined;
2467 a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &a_limb));
2468
2469 var b_limb: Limb = undefined;
2470 b_borrow = @boolToInt(@subWithOverflow(Limb, b[i], b_borrow, &b_limb));
2471
2472 r[i] = a_limb | b_limb;
2473 r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i]));
2474 }
2475
2476 // b is at least 1, so this should never underflow.
2477 assert(b_borrow == 0); // b was 0
2478
2479 // With b = 0 and b_borrow = 0 we get (-a - 1) | (-0 - 0) = (-a - 1) | 0 = -a - 1.
2480 while (i < a.len) : (i += 1) {
2481 a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &r[i]));
2482 r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i]));
2483 }
2484
2485 assert(a_borrow == 0); // a was 0.
2486
2487 // The final addition can overflow here, so we need to keep that in mind.
2488 r[i] = r_carry;
2489
2490 return false;
22612491 }
22622492}
22632493
......@@ -2265,6 +2495,8 @@ fn lland(r: []Limb, a: []const Limb, b: []const Limb) void {
22652495// r may alias.
22662496// a and b must not be -0.
22672497// Returns `true` when the result is positive.
2498// If the sign of a and b is equal, then r requires at least `max(a.len, b.len)` limbs are required.
2499// Otherwise, r requires at least `max(a.len, b.len) + 1` limbs.
22682500fn llsignedxor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_positive: bool) bool {
22692501 @setRuntimeSafety(debug_safety);
22702502 assert(a.len != 0 and b.len != 0);
......@@ -2308,7 +2540,12 @@ fn llsignedxor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_
23082540 r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i]));
23092541 }
23102542
2311 r[i] = r_carry;
2543 // If both inputs don't share the same sign, an extra limb is required.
2544 if (a_positive != b_positive) {
2545 r[i] = r_carry;
2546 } else {
2547 assert(r_carry == 0);
2548 }
23122549
23132550 assert(a_borrow == 0);
23142551 assert(b_borrow == 0);
lib/std/math/big/int_test.zig+143
......@@ -1365,6 +1365,83 @@ test "big.int bitwise and multi-limb" {
13651365 try testing.expect((try a.to(u128)) == 0);
13661366}
13671367
1368test "big.int bitwise and negative-positive simple" {
1369 var a = try Managed.initSet(testing.allocator, -0xffffffff11111111);
1370 defer a.deinit();
1371 var b = try Managed.initSet(testing.allocator, 0xeeeeeeee22222222);
1372 defer b.deinit();
1373
1374 try a.bitAnd(a, b);
1375
1376 try testing.expect((try a.to(u64)) == 0x22222222);
1377}
1378
1379test "big.int bitwise and negative-positive multi-limb" {
1380 var a = try Managed.initSet(testing.allocator, -maxInt(Limb) - 1);
1381 defer a.deinit();
1382 var b = try Managed.initSet(testing.allocator, maxInt(Limb));
1383 defer b.deinit();
1384
1385 try a.bitAnd(a, b);
1386
1387 try testing.expect(a.eqZero());
1388}
1389
1390test "big.int bitwise and positive-negative simple" {
1391 var a = try Managed.initSet(testing.allocator, 0xffffffff11111111);
1392 defer a.deinit();
1393 var b = try Managed.initSet(testing.allocator, -0xeeeeeeee22222222);
1394 defer b.deinit();
1395
1396 try a.bitAnd(a, b);
1397
1398 try testing.expect((try a.to(u64)) == 0x1111111111111110);
1399}
1400
1401test "big.int bitwise and positive-negative multi-limb" {
1402 var a = try Managed.initSet(testing.allocator, maxInt(Limb));
1403 defer a.deinit();
1404 var b = try Managed.initSet(testing.allocator, -maxInt(Limb) - 1);
1405 defer b.deinit();
1406
1407 try a.bitAnd(a, b);
1408
1409 try testing.expect(a.eqZero());
1410}
1411
1412test "big.int bitwise and negative-negative simple" {
1413 var a = try Managed.initSet(testing.allocator, -0xffffffff11111111);
1414 defer a.deinit();
1415 var b = try Managed.initSet(testing.allocator, -0xeeeeeeee22222222);
1416 defer b.deinit();
1417
1418 try a.bitAnd(a, b);
1419
1420 try testing.expect((try a.to(i128)) == -0xffffffff33333332);
1421}
1422
1423test "big.int bitwise and negative-negative multi-limb" {
1424 var a = try Managed.initSet(testing.allocator, -maxInt(Limb) - 1);
1425 defer a.deinit();
1426 var b = try Managed.initSet(testing.allocator, -maxInt(Limb) - 2);
1427 defer b.deinit();
1428
1429 try a.bitAnd(a, b);
1430
1431 try testing.expect((try a.to(i128)) == -maxInt(Limb) * 2 - 2);
1432}
1433
1434test "big.int bitwise and negative overflow" {
1435 var a = try Managed.initSet(testing.allocator, -maxInt(Limb));
1436 defer a.deinit();
1437 var b = try Managed.initSet(testing.allocator, -2);
1438 defer b.deinit();
1439
1440 try a.bitAnd(a, b);
1441
1442 try testing.expect((try a.to(SignedDoubleLimb)) == -maxInt(Limb) - 1);
1443}
1444
13681445test "big.int bitwise xor simple" {
13691446 var a = try Managed.initSet(testing.allocator, 0xffffffff11111111);
13701447 defer a.deinit();
......@@ -1476,6 +1553,72 @@ test "big.int bitwise or multi-limb" {
14761553 try testing.expect((try a.to(DoubleLimb)) == (maxInt(Limb) + 1) + maxInt(Limb));
14771554}
14781555
1556test "big.int bitwise or negative-positive simple" {
1557 var a = try Managed.initSet(testing.allocator, -0xffffffff11111111);
1558 defer a.deinit();
1559 var b = try Managed.initSet(testing.allocator, 0xeeeeeeee22222222);
1560 defer b.deinit();
1561
1562 try a.bitOr(a, b);
1563
1564 try testing.expect((try a.to(i64)) == -0x1111111111111111);
1565}
1566
1567test "big.int bitwise or negative-positive multi-limb" {
1568 var a = try Managed.initSet(testing.allocator, -maxInt(Limb) - 1);
1569 defer a.deinit();
1570 var b = try Managed.initSet(testing.allocator, 1);
1571 defer b.deinit();
1572
1573 try a.bitOr(a, b);
1574
1575 try testing.expect((try a.to(SignedDoubleLimb)) == -maxInt(Limb));
1576}
1577
1578test "big.int bitwise or positive-negative simple" {
1579 var a = try Managed.initSet(testing.allocator, 0xffffffff11111111);
1580 defer a.deinit();
1581 var b = try Managed.initSet(testing.allocator, -0xeeeeeeee22222222);
1582 defer b.deinit();
1583
1584 try a.bitOr(a, b);
1585
1586 try testing.expect((try a.to(i64)) == -0x22222221);
1587}
1588
1589test "big.int bitwise or positive-negative multi-limb" {
1590 var a = try Managed.initSet(testing.allocator, maxInt(Limb) + 1);
1591 defer a.deinit();
1592 var b = try Managed.initSet(testing.allocator, -1);
1593 defer b.deinit();
1594
1595 try a.bitOr(a, b);
1596
1597 try testing.expect((try a.to(SignedDoubleLimb)) == -1);
1598}
1599
1600test "big.int bitwise or negative-negative simple" {
1601 var a = try Managed.initSet(testing.allocator, -0xffffffff11111111);
1602 defer a.deinit();
1603 var b = try Managed.initSet(testing.allocator, -0xeeeeeeee22222222);
1604 defer b.deinit();
1605
1606 try a.bitOr(a, b);
1607
1608 try testing.expect((try a.to(i128)) == -0xeeeeeeee00000001);
1609}
1610
1611test "big.int bitwise or negative-negative multi-limb" {
1612 var a = try Managed.initSet(testing.allocator, -maxInt(Limb) - 1);
1613 defer a.deinit();
1614 var b = try Managed.initSet(testing.allocator, -maxInt(Limb));
1615 defer b.deinit();
1616
1617 try a.bitOr(a, b);
1618
1619 try testing.expect((try a.to(SignedDoubleLimb)) == -maxInt(Limb));
1620}
1621
14791622test "big.int var args" {
14801623 var a = try Managed.initSet(testing.allocator, 5);
14811624 defer a.deinit();