authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-09-23 06:19:08+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-09-23 06:19:08+02:00
log351e4f07cebc8299c107bd3de760efe17d184af7
tree570d658f7b83c2f76676523f1cb85969b33acbdd
parentf5c27dd11aeac3bb4647396a0e420c649b30f468

big ints: 2s complement signed and + or fixes


2 files changed, 229 insertions(+), 32 deletions(-)

lib/std/math/big/int.zig+149-29
...@@ -570,26 +570,36 @@ pub const Mutable = struct {...@@ -570,26 +570,36 @@ pub const Mutable = struct {
570570
571 if (a.limbs.len >= b.limbs.len) {571 if (a.limbs.len >= b.limbs.len) {
572 r.positive = llsignedor(r.limbs, a.limbs, a.positive, b.limbs, b.positive);572 r.positive = llsignedor(r.limbs, a.limbs, a.positive, b.limbs, b.positive);
573 r.normalize(a.limbs.len);573 r.normalize(if (b.positive) a.limbs.len else b.limbs.len);
574 } else {574 } else {
575 r.positive = llsignedor(r.limbs, b.limbs, b.positive, a.limbs, a.positive);575 r.positive = llsignedor(r.limbs, b.limbs, b.positive, a.limbs, a.positive);
576 r.normalize(b.limbs.len);576 r.normalize(if (a.positive) b.limbs.len else a.limbs.len);
577 }577 }
578 }578 }
579579
580 /// r = a & b580 /// r = a & b under 2s complement semantics.
581 /// r may alias with a or b.581 /// r may alias with a or b.
582 ///582 ///
583 /// 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`.
584 pub fn bitAnd(r: *Mutable, a: Const, b: Const) void {586 pub fn bitAnd(r: *Mutable, a: Const, b: Const) void {
585 if (a.limbs.len > b.limbs.len) {587 // Trivial cases, llsignedand does not support zero.
586 lland(r.limbs[0..], a.limbs[0..a.limbs.len], b.limbs[0..b.limbs.len]);588 if (a.eqZero()) {
587 r.normalize(b.limbs.len);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);
588 } else {599 } else {
589 lland(r.limbs[0..], b.limbs[0..b.limbs.len], a.limbs[0..a.limbs.len]);600 r.positive = llsignedand(r.limbs, b.limbs, b.positive, a.limbs, a.positive);
590 r.normalize(a.limbs.len);601 r.normalize(if (a.positive or b.positive) a.limbs.len else b.limbs.len + 1);
591 }602 }
592 r.positive = a.positive and b.positive;
593 }603 }
594604
595 /// r = a ^ b under 2s complement semantics.605 /// r = a ^ b under 2s complement semantics.
...@@ -1855,7 +1865,11 @@ pub const Managed = struct {...@@ -1855,7 +1865,11 @@ pub const Managed = struct {
18551865
1856 /// r = a & b1866 /// r = a & b
1857 pub fn bitAnd(r: *Managed, a: Managed, b: Managed) !void {1867 pub fn bitAnd(r: *Managed, a: Managed, b: Managed) !void {
1858 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);
1859 var m = r.toMutable();1873 var m = r.toMutable();
1860 m.bitAnd(a.toConst(), b.toConst());1874 m.bitAnd(a.toConst(), b.toConst());
1861 r.setMetadata(m.positive, m.len);1875 r.setMetadata(m.positive, m.len);
...@@ -2244,13 +2258,19 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) void {...@@ -2244,13 +2258,19 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) void {
2244 }2258 }
2245}2259}
22462260
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.
2247fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_positive: bool) bool {2267fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_positive: bool) bool {
2248 @setRuntimeSafety(debug_safety);2268 @setRuntimeSafety(debug_safety);
2249 assert(r.len >= a.len);2269 assert(r.len >= a.len);
2250 assert(a.len >= b.len);2270 assert(a.len >= b.len);
22512271
2252 if (a_positive and b_positive) {2272 if (a_positive and b_positive) {
2253 // Trivial case, result is positive.,2273 // Trivial case, result is positive.
2254 var i: usize = 0;2274 var i: usize = 0;
2255 while (i < b.len) : (i += 1) {2275 while (i < b.len) : (i += 1) {
2256 r[i] = a[i] | b[i];2276 r[i] = a[i] | b[i];
...@@ -2261,12 +2281,12 @@ fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_p...@@ -2261,12 +2281,12 @@ fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_p
22612281
2262 return true;2282 return true;
2263 } else if (!a_positive and b_positive) {2283 } else if (!a_positive and b_positive) {
2284 // Result is negative.
2264 // r = (--a) | b2285 // r = (--a) | b
2265 // = ~(-a - 1) | b2286 // = ~(-a - 1) | b
2266 // = ~(-a - 1) | ~~b2287 // = ~(-a - 1) | ~~b
2267 // = ~((-a - 1) & ~b)2288 // = ~((-a - 1) & ~b)
2268 // = -(((-a - 1) & ~b) + 1)2289 // = -(((-a - 1) & ~b) + 1)
2269 // result is negative.
22702290
2271 var i: usize = 0;2291 var i: usize = 0;
2272 var a_borrow: u1 = 1;2292 var a_borrow: u1 = 1;
...@@ -2280,25 +2300,29 @@ fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_p...@@ -2280,25 +2300,29 @@ fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_p
2280 r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i]));2300 r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i]));
2281 }2301 }
22822302
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
2283 // With b = 0, we get (-a - 1) & ~0 = -a - 1.2309 // With b = 0, we get (-a - 1) & ~0 = -a - 1.
2284 while (i < a.len) : (i += 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) {
2285 a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &r[i]));2313 a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &r[i]));
2286 r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i]));
2287 }2314 }
22882315
2289 assert(a_borrow == 0); // a was 0.2316 assert(a_borrow == 0); // a was 0.
22902317
2291 // Can never overflow because a_borrow would need to equal 1.
2292 assert(r_carry == 0);
2293
2294 return false;2318 return false;
2295 } else if (a_positive and !b_positive) {2319 } else if (a_positive and !b_positive) {
2320 // Result is negative.
2296 // r = a | (--b)2321 // r = a | (--b)
2297 // = a | ~(-b - 1)2322 // = a | ~(-b - 1)
2298 // = ~~a | ~(-b - 1)2323 // = ~~a | ~(-b - 1)
2299 // = ~(~a & (-b - 1))2324 // = ~(~a & (-b - 1))
2300 // = -((~a & (-b - 1)) + 1)2325 // = -((~a & (-b - 1)) + 1)
2301 // result is negative.
23022326
2303 var i: usize = 0;2327 var i: usize = 0;
2304 var b_borrow: u1 = 1;2328 var b_borrow: u1 = 1;
...@@ -2315,21 +2339,20 @@ fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_p...@@ -2315,21 +2339,20 @@ fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_p
2315 // b is at least 1, so this should never underflow.2339 // b is at least 1, so this should never underflow.
2316 assert(b_borrow == 0); // b was 02340 assert(b_borrow == 0); // b was 0
23172341
2318 // Can never overflow because in order for b_limb to be maxInt(Limb),2342 // x & ~a can only clear bits, so (x & ~a) <= x, meaning (-b - 1) + 1 never overflows.
2319 // b_borrow would need to equal 1.
2320 assert(r_carry == 0);2343 assert(r_carry == 0);
23212344
2322 // With b = 0 and b_borrow = 0, we get ~a & (-0 - 0) = ~a & 0 = 0.2345 // With b = 0 and b_borrow = 0, we get ~a & (-0 - 0) = ~a & 0 = 0.
2323 mem.set(Limb, r[i..a.len], 0);2346 // Omit setting the upper bytes, just deal with those when calling llsignedor.
23242347
2325 return false;2348 return false;
2326 } else {2349 } else {
2350 // Result is negative.
2327 // r = (--a) | (--b)2351 // r = (--a) | (--b)
2328 // = ~(-a - 1) | ~(-b - 1)2352 // = ~(-a - 1) | ~(-b - 1)
2329 // = ~((-a - 1) & (-b - 1))2353 // = ~((-a - 1) & (-b - 1))
2330 // = -(~(~((-a - 1) & (-b - 1))) + 1)2354 // = -(~(~((-a - 1) & (-b - 1))) + 1)
2331 // = -((-a - 1) & (-b - 1) + 1)2355 // = -((-a - 1) & (-b - 1) + 1)
2332 // result is negative.
23332356
2334 var i: usize = 0;2357 var i: usize = 0;
2335 var a_borrow: u1 = 1;2358 var a_borrow: u1 = 1;
...@@ -2352,22 +2375,119 @@ fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_p...@@ -2352,22 +2375,119 @@ fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_p
23522375
2353 // Can never overflow because in order for b_limb to be maxInt(Limb),2376 // Can never overflow because in order for b_limb to be maxInt(Limb),
2354 // b_borrow would need to equal 1.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.
2355 assert(r_carry == 0);2381 assert(r_carry == 0);
23562382
2357 // With b = 0 and b_borrow = 0 we get (-a - 1) & (-0 - 0) = (-a - 1) & 0 = 0.2383 // With b = 0 and b_borrow = 0 we get (-a - 1) & (-0 - 0) = (-a - 1) & 0 = 0.
2358 mem.set(Limb, r[i..a.len], 0);2384 // Omit setting the upper bytes, just deal with those when calling llsignedor.
2359 return false;2385 return false;
2360 }2386 }
2361}2387}
23622388
2363fn 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 {
2364 @setRuntimeSafety(debug_safety);2396 @setRuntimeSafety(debug_safety);
2365 assert(r.len >= b.len);2397 assert(a.len != 0 and b.len != 0);
2398 assert(r.len >= a.len);
2366 assert(a.len >= b.len);2399 assert(a.len >= b.len);
23672400
2368 var i: usize = 0;2401 if (a_positive and b_positive) {
2369 while (i < b.len) : (i += 1) {2402 // Trivial case, result is positive.
2370 r[i] = a[i] & b[i];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;
2371 }2491 }
2372}2492}
23732493
lib/std/math/big/int_test.zig+80-3
...@@ -1365,6 +1365,83 @@ test "big.int bitwise and multi-limb" {...@@ -1365,6 +1365,83 @@ test "big.int bitwise and multi-limb" {
1365 try testing.expect((try a.to(u128)) == 0);1365 try testing.expect((try a.to(u128)) == 0);
1366}1366}
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
1368test "big.int bitwise xor simple" {1445test "big.int bitwise xor simple" {
1369 var a = try Managed.initSet(testing.allocator, 0xffffffff11111111);1446 var a = try Managed.initSet(testing.allocator, 0xffffffff11111111);
1370 defer a.deinit();1447 defer a.deinit();
...@@ -1521,14 +1598,14 @@ test "big.int bitwise or positive-negative multi-limb" {...@@ -1521,14 +1598,14 @@ test "big.int bitwise or positive-negative multi-limb" {
1521}1598}
15221599
1523test "big.int bitwise or negative-negative simple" {1600test "big.int bitwise or negative-negative simple" {
1524 var a = try Managed.initSet(testing.allocator, -0x0fffffff11111111);1601 var a = try Managed.initSet(testing.allocator, -0xffffffff11111111);
1525 defer a.deinit();1602 defer a.deinit();
1526 var b = try Managed.initSet(testing.allocator, -0x0eeeeeee22222222);1603 var b = try Managed.initSet(testing.allocator, -0xeeeeeeee22222222);
1527 defer b.deinit();1604 defer b.deinit();
15281605
1529 try a.bitOr(a, b);1606 try a.bitOr(a, b);
15301607
1531 try testing.expect((try a.to(i64)) == -0xeeeeeee00000001);1608 try testing.expect((try a.to(i128)) == -0xeeeeeeee00000001);
1532}1609}
15331610
1534test "big.int bitwise or negative-negative multi-limb" {1611test "big.int bitwise or negative-negative multi-limb" {