Commit f1b3a90ef6
Changed files (2)
lib
std
math
lib/std/math/big/int.zig
@@ -86,6 +86,15 @@ pub fn addMulLimbWithCarry(a: Limb, b: Limb, c: Limb, carry: *Limb) Limb {
return r1;
}
+/// Used to indicate either limit of a 2s-complement integer.
+pub const TwosCompIntLimit = enum {
+ // The low limit, either 0x00 (unsigned) or (-)0x80 (signed) for an 8-bit integer.
+ min,
+
+ // The high limit, either 0xFF (unsigned) or 0x7F (signed) for an 8-bit integer.
+ max,
+};
+
/// A arbitrary-precision big integer, with a fixed set of mutable limbs.
pub const Mutable = struct {
/// Raw digits. These are:
@@ -287,6 +296,75 @@ pub const Mutable = struct {
self.positive = positive;
}
+ /// Set self to either bound of a 2s-complement integer.
+ /// Note: The result is still sign-magnitude, not twos complement! In order to convert the
+ /// result to twos complement, it is sufficient to take the absolute value.
+ ///
+ /// Asserts the result fits in `r`. An upper bound on the number of limbs needed by
+ /// r is `calcTwosCompLimbCount(bit_count)`.
+ pub fn setTwosCompIntLimit(
+ r: *Mutable,
+ limit: TwosCompIntLimit,
+ signedness: std.builtin.Signedness,
+ bit_count: usize
+ ) void {
+ // Handle zero-bit types.
+ if (bit_count == 0) {
+ r.set(0);
+ return;
+ }
+
+ const req_limbs = calcTwosCompLimbCount(bit_count);
+ const bit = @truncate(Log2Limb, bit_count - 1);
+ const signmask = @as(Limb, 1) << bit; // 0b0..010..0 where 1 is the sign bit.
+ const mask = (signmask << 1) -% 1; // 0b0..011..1 where the leftmost 1 is the sign bit.
+
+ r.positive = true;
+
+ switch (signedness) {
+ .signed => switch (limit) {
+ .min => {
+ // Negative bound, signed = -0x80.
+ r.len = req_limbs;
+ mem.set(Limb, r.limbs[0..r.len - 1], 0);
+ r.limbs[r.len - 1] = signmask;
+ r.positive = false;
+ },
+ .max => {
+ // Positive bound, signed = 0x7F
+ // Note, in this branch we need to normalize because the first bit is
+ // supposed to be 0.
+
+ // Special case for 1-bit integers.
+ if (bit_count == 1) {
+ r.set(0);
+ } else {
+ const new_req_limbs = calcTwosCompLimbCount(bit_count - 1);
+ const msb = @truncate(Log2Limb, bit_count - 2);
+ const new_signmask = @as(Limb, 1) << msb; // 0b0..010..0 where 1 is the sign bit.
+ const new_mask = (new_signmask << 1) -% 1; // 0b0..001..1 where the rightmost 0 is the sign bit.
+
+ r.len = new_req_limbs;
+ std.mem.set(Limb, r.limbs[0..r.len - 1], maxInt(Limb));
+ r.limbs[r.len - 1] = new_mask;
+ }
+ },
+ },
+ .unsigned => switch (limit) {
+ .min => {
+ // Min bound, unsigned = 0x00
+ r.set(0);
+ },
+ .max => {
+ // Max bound, unsigned = 0xFF
+ r.len = req_limbs;
+ std.mem.set(Limb, r.limbs[0..r.len - 1], maxInt(Limb));
+ r.limbs[r.len - 1] = mask;
+ },
+ },
+ }
+ }
+
/// r = a + scalar
///
/// r and a may be aliases.
@@ -335,7 +413,6 @@ pub const Mutable = struct {
}
/// r = a + b
- ///
/// r, a and b may be aliases.
///
/// Asserts the result fits in `r`. An upper bound on the number of limbs needed by
@@ -353,8 +430,8 @@ pub const Mutable = struct {
}
/// r = a + b with 2s-complement wrapping semantics.
- ///
/// r, a and b may be aliases
+ ///
/// Asserts the result fits in `r`. An upper bound on the number of limbs needed by
/// r is `calcTwosCompLimbCount(bit_count)`.
pub fn addWrap(r: *Mutable, a: Const, b: Const, signedness: std.builtin.Signedness, bit_count: usize) void {
@@ -374,7 +451,8 @@ pub const Mutable = struct {
if (r.addCarry(x, y)) {
// There are two possibilities here:
- // - We overflowed req_limbs. In this case, the carry is ignored.
+ // - We overflowed req_limbs. In this case, the carry is ignored, as it would be removed by
+ // truncate anyway.
// - a and b had less elements than req_limbs, and those were overflowed. This case needs to be handled.
const msl = math.max(a.limbs.len, b.limbs.len);
if (msl < req_limbs) {
@@ -399,7 +477,7 @@ pub const Mutable = struct {
} else if (b.eqZero()) {
r.copy(a);
return false;
- } if (a.positive != b.positive) {
+ } else if (a.positive != b.positive) {
if (a.positive) {
// (a) - (-b) => a + b
return r.addCarry(a, b.abs());
@@ -478,7 +556,8 @@ pub const Mutable = struct {
if (r.subCarry(x, y)) {
// There are two possibilities here:
- // - We overflowed req_limbs. In this case, the carry is ignored.
+ // - We overflowed req_limbs. In this case, the carry is ignored, as it would be removed by
+ // truncate anyway.
// - a and b had less elements than req_limbs, and those were overflowed. This case needs to be handled.
const msl = math.max(a.limbs.len, b.limbs.len);
if (msl < req_limbs) {
@@ -1131,8 +1210,8 @@ pub const Mutable = struct {
}
const bit = @truncate(Log2Limb, bit_count - 1);
- const signmask = @as(Limb, 1) << bit;
- const mask = (signmask << 1) -% 1;
+ const signmask = @as(Limb, 1) << bit; // 0b0..010...0 where 1 is the sign bit.
+ const mask = (signmask << 1) -% 1; // 0b0..01..1 where the leftmost 1 is the sign bit.
if (!a.positive) {
// Convert the integer from sign-magnitude into twos-complement.
@@ -1871,6 +1950,21 @@ pub const Managed = struct {
self.setMetadata(m.positive, m.len);
}
+ /// Set self to either bound of a 2s-complement integer.
+ /// Note: The result is still sign-magnitude, not twos complement! In order to convert the
+ /// result to twos complement, it is sufficient to take the absolute value.
+ pub fn setTwosCompIntLimit(
+ r: *Managed,
+ limit: TwosCompIntLimit,
+ signedness: std.builtin.Signedness,
+ bit_count: usize
+ ) !void {
+ try r.ensureCapacity(calcTwosCompLimbCount(bit_count));
+ var m = r.toMutable();
+ m.setTwosCompIntLimit(limit, signedness, bit_count);
+ r.setMetadata(m.positive, m.len);
+ }
+
/// Converts self to a string in the requested base. Memory is allocated from the provided
/// allocator and not the one present in self.
pub fn toString(self: Managed, allocator: *Allocator, base: u8, case: std.fmt.Case) ![]u8 {
@@ -2184,7 +2278,7 @@ pub const Managed = struct {
}
}
- // r = truncate(Int(signedness, bit_count), a)
+ /// r = truncate(Int(signedness, bit_count), a)
pub fn truncate(r: *Managed, a: Const, signedness: std.builtin.Signedness, bit_count: usize) !void {
try r.ensureCapacity(calcTwosCompLimbCount(bit_count));
var m = r.toMutable();
lib/std/math/big/int_test.zig
@@ -269,6 +269,36 @@ test "big.int string set bad base error" {
try testing.expectError(error.InvalidBase, a.setString(45, "10"));
}
+test "big.int twos complement limit set" {
+ const test_types = [_]type{
+ u64,
+ i64,
+ u1,
+ i1,
+ u0,
+ i0,
+ u65,
+ i65,
+ };
+
+ inline for (test_types) |T| {
+ // To work around 'control flow attempts to use compile-time variable at runtime'
+ const U = T;
+ const int_info = @typeInfo(U).Int;
+
+ var a = try Managed.init(testing.allocator);
+ defer a.deinit();
+
+ try a.setTwosCompIntLimit(.max, int_info.signedness, int_info.bits);
+ var max: U = maxInt(U);
+ try testing.expect(max == try a.to(U));
+
+ try a.setTwosCompIntLimit(.min, int_info.signedness, int_info.bits);
+ var min: U = minInt(U);
+ try testing.expect(min == try a.to(U));
+ }
+}
+
test "big.int string to" {
var a = try Managed.initSet(testing.allocator, 120317241209124781241290847124);
defer a.deinit();