authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-10-15 20:28:41+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-10-16 11:32:05+02:00
logefa4f76c8bb9cbeacc1bcda89d95db4120c821d5
treec617bd91e197d2aa70169078fb22857650f9f258
parent1df926b24e555473db8d9cf7b2a5c156ef34eb52

big ints: Saturating left shift + tests


2 files changed, 174 insertions(+), 3 deletions(-)

lib/std/math/big/int.zig+88-3
......@@ -835,6 +835,75 @@ pub const Mutable = struct {
835835 r.positive = a.positive;
836836 }
837837
838 /// r = a <<| shift with 2s-complement saturating semantics.
839 ///
840 /// r and a may alias.
841 ///
842 /// Asserts there is enough memory to fit the result. The upper bound Limb count is
843 /// r is `calcTwosCompLimbCount(bit_count)`.
844 pub fn shiftLeftSat(r: *Mutable, a: Const, shift: usize, signedness: std.builtin.Signedness, bit_count: usize) void {
845 // Special case: When the argument is negative, but the result is supposed to be unsigned,
846 // return 0 in all cases.
847 if (!a.positive and signedness == .unsigned) {
848 r.set(0);
849 return;
850 }
851
852 // Check whether the shift is going to overflow. This is the case
853 // when (in 2s complement) any bit above `bit_count - shift` is set in the unshifted value.
854 // Note, the sign bit is not counted here.
855
856 // Handle shifts larger than the target type. This also deals with
857 // 0-bit integers.
858 if (bit_count <= shift) {
859 // In this case, there is only no overflow if `a` is zero.
860 if (a.eqZero()) {
861 r.set(0);
862 } else {
863 r.setTwosCompIntLimit(if (a.positive) .max else .min, signedness, bit_count);
864 }
865 return;
866 }
867
868 const checkbit = bit_count - shift - @boolToInt(signedness == .signed);
869 // If `checkbit` and more significant bits are zero, no overflow will take place.
870
871 if (checkbit >= a.limbs.len * limb_bits) {
872 // `checkbit` is outside the range of a, so definitely no overflow will take place. We
873 // can defer to a normal shift.
874 // Note that if `a` is normalized (which we assume), this checks for set bits in the upper limbs.
875
876 // Note, in this case r should already have enough limbs required to perform the normal shift.
877 // In this case the shift of the most significant limb may still overflow.
878 r.shiftLeft(a, shift);
879 return;
880 } else if (checkbit < (a.limbs.len - 1) * limb_bits) {
881 // `checkbit` is not in the most significant limb. If `a` is normalized the most significant
882 // limb will not be zero, so in this case we need to saturate. Note that `a.limbs.len` must be
883 // at least one according to normalization rules.
884
885 r.setTwosCompIntLimit(if (a.positive) .max else .min, signedness, bit_count);
886 return;
887 }
888
889 // Generate a mask with the bits to check in the most signficant limb. We'll need to check
890 // all bits with equal or more significance than checkbit.
891 // const msb = @truncate(Log2Limb, checkbit);
892 // const checkmask = (@as(Limb, 1) << msb) -% 1;
893
894 if (a.limbs[a.limbs.len - 1] >> @truncate(Log2Limb, checkbit) != 0) {
895 // Need to saturate.
896 r.setTwosCompIntLimit(if (a.positive) .max else .min, signedness, bit_count);
897 return;
898 }
899
900 // This shift should not be able to overflow, so invoke llshl and normalize manually
901 // to avoid the extra required limb.
902 llshl(r.limbs[0..], a.limbs[0..a.limbs.len], shift);
903 r.normalize(a.limbs.len + (shift / limb_bits));
904 r.positive = a.positive;
905 }
906
838907 /// r = a >> shift
839908 /// r and a may alias.
840909 ///
......@@ -2401,6 +2470,14 @@ pub const Managed = struct {
24012470 r.setMetadata(m.positive, m.len);
24022471 }
24032472
2473 /// r = a <<| shift with 2s-complement saturating semantics.
2474 pub fn shiftLeftSat(r: *Managed, a: Managed, shift: usize, signedness: std.builtin.Signedness, bit_count: usize) !void {
2475 try r.ensureTwosCompCapacity(bit_count);
2476 var m = r.toMutable();
2477 m.shiftLeftSat(a.toConst(), shift, signedness, bit_count);
2478 r.setMetadata(m.positive, m.len);
2479 }
2480
24042481 /// r = a >> shift
24052482 pub fn shiftRight(r: *Managed, a: Managed, shift: usize) !void {
24062483 if (a.len() <= shift / limb_bits) {
......@@ -2949,10 +3026,18 @@ fn lldiv1(quo: []Limb, rem: *Limb, a: []const Limb, b: Limb) void {
29493026fn llshl(r: []Limb, a: []const Limb, shift: usize) void {
29503027 @setRuntimeSafety(debug_safety);
29513028 assert(a.len >= 1);
2952 assert(r.len >= a.len + (shift / limb_bits) + 1);
3029
3030 const interior_limb_shift = @truncate(Log2Limb, shift);
3031
3032 // We only need the extra limb if the shift of the last element overflows.
3033 // This is useful for the implementation of `shiftLeftSat`.
3034 if (a[a.len - 1] << interior_limb_shift >> interior_limb_shift != a[a.len - 1]) {
3035 assert(r.len >= a.len + (shift / limb_bits) + 1);
3036 } else {
3037 assert(r.len >= a.len + (shift / limb_bits));
3038 }
29533039
29543040 const limb_shift = shift / limb_bits + 1;
2955 const interior_limb_shift = @intCast(Log2Limb, shift % limb_bits);
29563041
29573042 var carry: Limb = 0;
29583043 var i: usize = 0;
......@@ -2979,7 +3064,7 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) void {
29793064 assert(r.len >= a.len - (shift / limb_bits));
29803065
29813066 const limb_shift = shift / limb_bits;
2982 const interior_limb_shift = @intCast(Log2Limb, shift % limb_bits);
3067 const interior_limb_shift = @truncate(Log2Limb, shift);
29833068
29843069 var carry: Limb = 0;
29853070 var i: usize = 0;
lib/std/math/big/int_test.zig+86
......@@ -1773,6 +1773,92 @@ test "big.int shift-left negative" {
17731773 try testing.expect((try a.to(i32)) == -10 >> 1232);
17741774}
17751775
1776test "big.int sat shift-left simple unsigned" {
1777 var a = try Managed.initSet(testing.allocator, 0xffff);
1778 defer a.deinit();
1779 try a.shiftLeftSat(a, 16, .unsigned, 21);
1780
1781 try testing.expect((try a.to(u64)) == 0x1fffff);
1782}
1783
1784test "big.int sat shift-left simple unsigned no sat" {
1785 var a = try Managed.initSet(testing.allocator, 1);
1786 defer a.deinit();
1787 try a.shiftLeftSat(a, 16, .unsigned, 21);
1788
1789 try testing.expect((try a.to(u64)) == 0x10000);
1790}
1791
1792test "big.int sat shift-left multi unsigned" {
1793 var a = try Managed.initSet(testing.allocator, 16);
1794 defer a.deinit();
1795 try a.shiftLeftSat(a, @bitSizeOf(DoubleLimb) - 3, .unsigned, @bitSizeOf(DoubleLimb) - 1);
1796
1797 try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb) >> 1);
1798}
1799
1800test "big.int sat shift-left unsigned shift > bitcount" {
1801 var a = try Managed.initSet(testing.allocator, 1);
1802 defer a.deinit();
1803 try a.shiftLeftSat(a, 10, .unsigned, 10);
1804
1805 try testing.expect((try a.to(u10)) == maxInt(u10));
1806}
1807
1808test "big.int sat shift-left unsigned zero" {
1809 var a = try Managed.initSet(testing.allocator, 0);
1810 defer a.deinit();
1811 try a.shiftLeftSat(a, 1, .unsigned, 0);
1812
1813 try testing.expect((try a.to(u64)) == 0);
1814}
1815
1816test "big.int sat shift-left unsigned negative" {
1817 var a = try Managed.initSet(testing.allocator, -100);
1818 defer a.deinit();
1819 try a.shiftLeftSat(a, 0, .unsigned, 0);
1820
1821 try testing.expect((try a.to(u64)) == 0);
1822}
1823
1824test "big.int sat shift-left signed simple negative" {
1825 var a = try Managed.initSet(testing.allocator, -100);
1826 defer a.deinit();
1827 try a.shiftLeftSat(a, 3, .signed, 10);
1828
1829 try testing.expect((try a.to(i10)) == minInt(i10));
1830}
1831
1832test "big.int sat shift-left signed simple positive" {
1833 var a = try Managed.initSet(testing.allocator, 100);
1834 defer a.deinit();
1835 try a.shiftLeftSat(a, 3, .signed, 10);
1836
1837 try testing.expect((try a.to(i10)) == maxInt(i10));
1838}
1839
1840test "big.int sat shift-left signed multi positive" {
1841 const x = 1;
1842 const shift = @bitSizeOf(SignedDoubleLimb) - 1;
1843
1844 var a = try Managed.initSet(testing.allocator, x);
1845 defer a.deinit();
1846 try a.shiftLeftSat(a, shift, .signed, @bitSizeOf(SignedDoubleLimb));
1847
1848 try testing.expect((try a.to(SignedDoubleLimb)) == @as(SignedDoubleLimb, x) <<| shift);
1849}
1850
1851test "big.int sat shift-left signed multi negative" {
1852 const x = -1;
1853 const shift = @bitSizeOf(SignedDoubleLimb) - 1;
1854
1855 var a = try Managed.initSet(testing.allocator, x);
1856 defer a.deinit();
1857 try a.shiftLeftSat(a, shift, .signed, @bitSizeOf(SignedDoubleLimb));
1858
1859 try testing.expect((try a.to(SignedDoubleLimb)) == @as(SignedDoubleLimb, x) <<| shift);
1860}
1861
17761862test "big.int bitwise and simple" {
17771863 var a = try Managed.initSet(testing.allocator, 0xffffffff11111111);
17781864 defer a.deinit();