authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-09-26 07:40:12+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-10-04 11:25:29+02:00
logb58cf6dab6ff64c3403d3776a430694534f7b818
treec9fd7d06f5d5b323762626dbd11b3eac8366363d
parent616b23c8159a5b643253299ce2493d3a5abd2b81

big ints: 2s complement truncate


1 files changed, 107 insertions(+), 0 deletions(-)

lib/std/math/big/int.zig+107
...@@ -58,6 +58,11 @@ pub fn calcPowLimbsBufferLen(a_bit_count: usize, y: usize) usize {...@@ -58,6 +58,11 @@ pub fn calcPowLimbsBufferLen(a_bit_count: usize, y: usize) usize {
58 return 2 + (a_bit_count * y + (limb_bits - 1)) / limb_bits;58 return 2 + (a_bit_count * y + (limb_bits - 1)) / limb_bits;
59}59}
6060
61// Compute the number of limbs required to store a 2s-complement number of `bit_count` bits.
62pub fn calcTwosCompLimbCount(bit_count: usize) usize {
63 return std.math.divCeil(usize, bit_count, @bitSizeOf(Limb)) catch unreachable;
64}
65
61/// a + b * c + *carry, sets carry to the overflow bits66/// a + b * c + *carry, sets carry to the overflow bits
62pub fn addMulLimbWithCarry(a: Limb, b: Limb, c: Limb, carry: *Limb) Limb {67pub fn addMulLimbWithCarry(a: Limb, b: Limb, c: Limb, carry: *Limb) Limb {
63 @setRuntimeSafety(debug_safety);68 @setRuntimeSafety(debug_safety);
...@@ -980,6 +985,91 @@ pub const Mutable = struct {...@@ -980,6 +985,91 @@ pub const Mutable = struct {
980 r.normalize(r.len);985 r.normalize(r.len);
981 }986 }
982987
988 /// Truncate an integer to a number of bits, following 2s-complement semantics.
989 /// r may alias a.
990 ///
991 /// Asserts `r` has enough storage to store the result.
992 /// The upper bound is `calcTwosCompLimbCount(a.len)`.
993 pub fn truncate(r: *Mutable, a: Const, signedness: std.builtin.Signedness, bit_count: usize) void {
994 const req_limbs = (bit_count + @bitSizeOf(Limb) - 1) / @bitSizeOf(Limb);
995
996 // Handle 0-bit integers.
997 if (req_limbs == 0 or a.eqZero()) {
998 r.set(0);
999 return;
1000 }
1001
1002 const bit = @truncate(Log2Limb, bit_count - 1);
1003 const signmask = @as(Limb, 1) << bit;
1004 const mask = (signmask << 1) -% 1;
1005
1006 if (!a.positive) {
1007 // Convert the integer from sign-magnitude into twos-complement.
1008 // -x = ~(x - 1)
1009 // Note, we simply take req_limbs * @bitSizeOf(Limb) as the
1010 // target bit count.
1011
1012 r.addScalar(a.abs(), -1);
1013
1014 // Zero-extend the result
1015 if (req_limbs > r.len) {
1016 mem.set(Limb, r.limbs[r.len .. req_limbs], 0);
1017 }
1018
1019 // Truncate to required number of limbs.
1020 assert(r.limbs.len >= req_limbs);
1021 r.len = req_limbs;
1022
1023 // Without truncating, we can already peek at the sign bit of the result here.
1024 // Note that it will be 0 if the result is negative, as we did not apply the flip here.
1025 // If the result is negative, we have
1026 // -(-x & mask)
1027 // = ~(~(x - 1) & mask) + 1
1028 // = ~(~((x - 1) | ~mask)) + 1
1029 // = ((x - 1) | ~mask)) + 1
1030 // Note, this is only valid for the target bits and not the upper bits
1031 // of the most significant limb. Those still need to be cleared.
1032 // Also note that `mask` is zero for all other bits, reducing to the identity.
1033 // This means that we still need to use & mask to clear off the upper bits.
1034
1035 if (signedness == .signed and r.limbs[r.len - 1] & signmask == 0) {
1036 // Re-add the one and negate to get the result.
1037 r.limbs[r.len - 1] &= mask;
1038 // Note, addition cannot require extra limbs here as we did a subtraction before.
1039 r.addScalar(r.toConst(), 1);
1040 r.normalize(r.len);
1041 r.positive = false;
1042 } else {
1043 llnot(r.limbs[0..r.len]);
1044 r.limbs[r.len - 1] &= mask;
1045 r.normalize(r.len);
1046 }
1047 } else {
1048 r.copy(a);
1049 if (r.len < req_limbs) {
1050 // Integer fits within target bits, no wrapping required.
1051 return;
1052 }
1053
1054 r.len = req_limbs;
1055 r.limbs[r.len - 1] &= mask;
1056 r.normalize(r.len);
1057
1058 if (signedness == .signed and r.limbs[r.len - 1] & signmask != 0) {
1059 // Convert 2s-complement back to sign-magnitude.
1060 // Sign-extend the upper bits so that they are inverted correctly.
1061 r.limbs[r.len - 1] |= ~mask;
1062 llnot(r.limbs[0..r.len]);
1063
1064 // Note, can only overflow if r holds 0xFFF...F which can only happen if
1065 // a holds 0.
1066 r.addScalar(r.toConst(), 1);
1067
1068 r.positive = false;
1069 }
1070 }
1071 }
1072
983 /// Normalize a possible sequence of leading zeros.1073 /// Normalize a possible sequence of leading zeros.
984 ///1074 ///
985 /// [1, 2, 3, 4, 0] -> [1, 2, 3, 4]1075 /// [1, 2, 3, 4, 0] -> [1, 2, 3, 4]
...@@ -1941,6 +2031,14 @@ pub const Managed = struct {...@@ -1941,6 +2031,14 @@ pub const Managed = struct {
1941 rma.setMetadata(rma_mut.positive, rma_mut.len);2031 rma.setMetadata(rma_mut.positive, rma_mut.len);
1942 }2032 }
1943 }2033 }
2034
2035 // r = truncate(Int(signedness, bit_count), a)
2036 pub fn truncate(r: *Managed, a: Const, signedness: std.builtin.Signedness, bit_count: usize) !void {
2037 try r.ensureCapacity(calcTwosCompLimbCount(a.limbs.len));
2038 var m = r.toMutable();
2039 m.truncate(a, signedness, bit_count);
2040 r.setMetadata(m.positive, m.len);
2041 }
1944};2042};
19452043
1946/// Knuth 4.3.1, Algorithm M.2044/// Knuth 4.3.1, Algorithm M.
...@@ -2270,6 +2368,15 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) void {...@@ -2270,6 +2368,15 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) void {
2270 }2368 }
2271}2369}
22722370
2371// r = ~r
2372fn llnot(r: []Limb) void {
2373 @setRuntimeSafety(debug_safety);
2374
2375 for (r) |*elem| {
2376 elem.* = ~elem.*;
2377 }
2378}
2379
2273// r = a | b with 2s complement semantics.2380// r = a | b with 2s complement semantics.
2274// r may alias.2381// r may alias.
2275// a and b must not be 0.2382// a and b must not be 0.