Commit ae442e2c29

David Rubin <daviru007@icloud.com>
2025-03-21 06:16:57
big.int: return normalized results from `{add,sub}Carry`
1 parent adee3ee
Changed files (3)
lib
std
src
lib/std/math/big/int.zig
@@ -76,9 +76,18 @@ pub fn calcSqrtLimbsBufferLen(a_bit_count: usize) usize {
     return a_limb_count + 3 * u_s_rem_limb_count + calcDivLimbsBufferLen(a_limb_count, u_s_rem_limb_count);
 }
 
-// Compute the number of limbs required to store a 2s-complement number of `bit_count` bits.
+/// Compute the number of limbs required to store a 2s-complement number of `bit_count` bits.
+pub fn calcNonZeroTwosCompLimbCount(bit_count: usize) usize {
+    assert(bit_count != 0);
+    return calcTwosCompLimbCount(bit_count);
+}
+
+/// Compute the number of limbs required to store a 2s-complement number of `bit_count` bits.
+///
+/// Special cases `bit_count == 0` to return 1. Zero-bit integers can only store the value zero
+/// and this big integer implementation stores zero using one limb.
 pub fn calcTwosCompLimbCount(bit_count: usize) usize {
-    return std.math.divCeil(usize, bit_count, @bitSizeOf(Limb)) catch unreachable;
+    return @max(std.math.divCeil(usize, bit_count, @bitSizeOf(Limb)) catch unreachable, 1);
 }
 
 /// a + b * c + *carry, sets carry to the overflow bits
@@ -188,8 +197,10 @@ pub const Mutable = struct {
         if (self.limbs.ptr != other.limbs.ptr) {
             @memcpy(self.limbs[0..other.limbs.len], other.limbs[0..other.limbs.len]);
         }
-        self.positive = other.positive;
-        self.len = other.limbs.len;
+        // Normalize before setting `positive` so the `eqlZero` doesn't need to iterate
+        // over the extra zero limbs.
+        self.normalize(other.limbs.len);
+        self.positive = other.positive or other.eqlZero();
     }
 
     /// Efficiently swap an Mutable with another. This swaps the limb pointers and a full copy is not
lib/std/math/big/int_test.zig
@@ -726,6 +726,34 @@ test "subWrap single-multi, signed, limb aligned" {
     try testing.expect((try a.toInt(SignedDoubleLimb)) == maxInt(SignedDoubleLimb));
 }
 
+test "addWrap returns normalized result" {
+    var x = try Managed.initSet(testing.allocator, 0);
+    defer x.deinit();
+    var y = try Managed.initSet(testing.allocator, 0);
+    defer y.deinit();
+
+    // make them both non normalized "-0"
+    x.setMetadata(false, 1);
+    y.setMetadata(false, 1);
+
+    var r = try Managed.init(testing.allocator);
+    defer r.deinit();
+    try testing.expect(!(try r.addWrap(&x, &y, .unsigned, 64)));
+    try testing.expect(r.isPositive() and r.len() == 1 and r.limbs[0] == 0);
+}
+
+test "subWrap returns normalized result" {
+    var x = try Managed.initSet(testing.allocator, 0);
+    defer x.deinit();
+    var y = try Managed.initSet(testing.allocator, 0);
+    defer y.deinit();
+
+    var r = try Managed.init(testing.allocator);
+    defer r.deinit();
+    try testing.expect(!(try r.subWrap(&x, &y, .unsigned, 64)));
+    try testing.expect(r.isPositive() and r.len() == 1 and r.limbs[0] == 0);
+}
+
 test "addSat single-single, unsigned" {
     var a = try Managed.initSet(testing.allocator, maxInt(u17) - 5);
     defer a.deinit();
src/Value.zig
@@ -2223,7 +2223,7 @@ pub fn shlSatScalar(
     const shift: usize = @intCast(rhs.toUnsignedInt(zcu));
     const limbs = try arena.alloc(
         std.math.big.Limb,
-        std.math.big.int.calcTwosCompLimbCount(info.bits) + 1,
+        std.math.big.int.calcTwosCompLimbCount(info.bits),
     );
     var result_bigint = BigIntMutable{
         .limbs = limbs,