big ints: setTwosCompIntLimit

This function can be used to initialize a big integer to either the upper
or lower limit of a 2s-complement integer. Note that the result is still
in sign-magnitude representation, though in order to convert it into twos
complement all one has to do is take the absolute value.
This commit is contained in:
Robin Voetter 2021-10-01 14:05:55 +02:00
parent dc1f698545
commit f1b3a90ef6
2 changed files with 132 additions and 8 deletions

View file

@ -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();

View file

@ -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();