Skip to main content

thruput_u256/
arith.rs

1//! Addition, subtraction, multiplication, shifts and bitwise operations.
2//!
3//! The operators mirror the primitives: wrap when `overflow-checks` is off,
4//! panic when it is on. Each `panic!` is guarded by [`OVERFLOW_CHECKS`],
5//! which const-folds away in a release contract build.
6//!
7//! # Inlining
8//!
9//! The big bodies here are `#[inline(never)]` on rv32 and carry no hint
10//! anywhere else. Only the rv32 half does anything.
11//!
12//! Inlining lets LLVM specialize each copy against known-zero limbs, so
13//! `.text` grows with the call-site count. Measured on rv32 against a probe
14//! with N distinct call sites, `inline(never)` breaks even at roughly 1.3 call
15//! sites for `widening_mul_u128`/`overflowing_mul`, 1.2 for `from_be_bytes` and 1.8
16//! for `to_be_bytes`. Real contracts sit at a handful -- the swap pair has
17//! four multiply sites -- and rv32 is where the 64 KiB artifact cap binds, so
18//! outlining pays there.
19//!
20//! Host builds get no hint at all. They are tests and the CLI's spec-tool,
21//! never gas-metered and never up against a code cap, so there is nothing
22//! there worth tuning for; an `#[inline]` on this side would have been noise.
23//! Keep the small bodies -- add/sub/shift/compare/`is_zero`/`from_limbs` --
24//! plain `#[inline]`: they are smaller than the call sequence that would
25//! replace them, which is a codesize claim, not a host-speed one.
26
27use crate::{LIMBS, U256};
28use core::ops::{
29    Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor,
30    BitXorAssign, Div, DivAssign, Mul, MulAssign, Not, Rem, RemAssign, Shl,
31    ShlAssign, Shr, ShrAssign, Sub, SubAssign,
32};
33
34/// `acc + a * b + carry`, split into (low, high) 32-bit halves.
35///
36/// Peaks at `(2^32-1)^2 + 2*(2^32-1) = 2^64-1`, so the `u64` never overflows.
37/// A `mul`/`mulhu` pair on rv32.
38#[inline(always)]
39const fn mac(acc: u32, a: u32, b: u32, carry: u32) -> (u32, u32) {
40    let t = (a as u64) * (b as u64) + (acc as u64) + (carry as u64);
41    (t as u32, (t >> 32) as u32)
42}
43
44impl U256 {
45    // -----------------------------------------------------------------------
46    // Addition and subtraction
47    // -----------------------------------------------------------------------
48
49    /// Wrapping addition, with a flag reporting whether it wrapped.
50    ///
51    /// This is OpenZeppelin's `Math.add512`, which returns the 512-bit sum as
52    /// `(high, low)` because Solidity has no wider type and no `bool`-carrying
53    /// tuple: its `high` is the carry, always zero or one. Here the carry is
54    /// the flag, so `(low, carry)` carries the same information the other way
55    /// round -- widen it with `U256::from(carry)` if you need the high word as
56    /// a value.
57    #[inline]
58    pub const fn overflowing_add(self, rhs: Self) -> (Self, bool) {
59        let mut out = [0u32; LIMBS];
60        let mut carry = 0u32;
61        let mut i = 0;
62        while i < LIMBS {
63            let t = (self.limbs[i] as u64)
64                + (rhs.limbs[i] as u64)
65                + (carry as u64);
66            out[i] = t as u32;
67            carry = (t >> 32) as u32;
68            i += 1;
69        }
70        (Self::from_limbs(out), carry != 0)
71    }
72
73    /// Wrapping subtraction, with a flag reporting whether it wrapped.
74    #[inline]
75    pub const fn overflowing_sub(self, rhs: Self) -> (Self, bool) {
76        let mut out = [0u32; LIMBS];
77        let mut borrow = 0u32;
78        let mut i = 0;
79        while i < LIMBS {
80            let t = (self.limbs[i] as u64)
81                .wrapping_sub(rhs.limbs[i] as u64)
82                .wrapping_sub(borrow as u64);
83            out[i] = t as u32;
84            borrow = ((t >> 32) & 1) as u32;
85            i += 1;
86        }
87        (Self::from_limbs(out), borrow != 0)
88    }
89
90    /// Addition, returning `None` on overflow.
91    #[inline]
92    pub const fn checked_add(self, rhs: Self) -> Option<Self> {
93        match self.overflowing_add(rhs) {
94            (v, false) => Some(v),
95            (_, true) => None,
96        }
97    }
98
99    /// Subtraction, returning `None` on overflow.
100    #[inline]
101    pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
102        match self.overflowing_sub(rhs) {
103            (v, false) => Some(v),
104            (_, true) => None,
105        }
106    }
107
108    /// Addition, wrapping on overflow.
109    #[inline]
110    pub const fn wrapping_add(self, rhs: Self) -> Self {
111        self.overflowing_add(rhs).0
112    }
113
114    /// Subtraction, wrapping on overflow.
115    #[inline]
116    pub const fn wrapping_sub(self, rhs: Self) -> Self {
117        self.overflowing_sub(rhs).0
118    }
119
120    /// Addition, saturating at [`U256::MAX`].
121    #[inline]
122    pub const fn saturating_add(self, rhs: Self) -> Self {
123        match self.overflowing_add(rhs) {
124            (v, false) => v,
125            (_, true) => Self::MAX,
126        }
127    }
128
129    /// Subtraction, saturating at [`U256::ZERO`].
130    #[inline]
131    pub const fn saturating_sub(self, rhs: Self) -> Self {
132        match self.overflowing_sub(rhs) {
133            (v, false) => v,
134            (_, true) => Self::ZERO,
135        }
136    }
137
138    /// Negation, with a flag reporting whether it wrapped.
139    ///
140    /// Everything but zero wraps -- there are no negative values here. The
141    /// wrapped result is the two's complement, which is what
142    /// [`I256`](crate::I256) is built on.
143    #[inline]
144    pub const fn overflowing_neg(self) -> (Self, bool) {
145        (Self::ZERO.wrapping_sub(self), !self.is_zero())
146    }
147
148    /// Negation, returning `None` for every value but zero.
149    #[inline]
150    pub const fn checked_neg(self) -> Option<Self> {
151        match self.overflowing_neg() {
152            (v, false) => Some(v),
153            (_, true) => None,
154        }
155    }
156
157    /// Negation, wrapping: the two's complement of `self`.
158    #[inline]
159    pub const fn wrapping_neg(self) -> Self {
160        Self::ZERO.wrapping_sub(self)
161    }
162
163    // -----------------------------------------------------------------------
164    // Multiplication
165    // -----------------------------------------------------------------------
166
167    /// The exact 256-bit product of two 128-bit values.
168    ///
169    /// The shape that dominates real call sites (`a * b / c` over token
170    /// amounts). Cannot overflow, so no check and no `Option`; sixteen
171    /// `mul`/`mulhu` pairs.
172    #[cfg_attr(target_arch = "riscv32", inline(never))]
173    pub const fn widening_mul_u128(a: u128, b: u128) -> Self {
174        let al = [
175            a as u32,
176            (a >> 32) as u32,
177            (a >> 64) as u32,
178            (a >> 96) as u32,
179        ];
180        let bl = [
181            b as u32,
182            (b >> 32) as u32,
183            (b >> 64) as u32,
184            (b >> 96) as u32,
185        ];
186
187        let mut out = [0u32; LIMBS];
188        let mut i = 0;
189        while i < 4 {
190            let mut carry = 0u32;
191            let mut j = 0;
192            while j < 4 {
193                let (lo, hi) = mac(out[i + j], al[i], bl[j], carry);
194                out[i + j] = lo;
195                carry = hi;
196                j += 1;
197            }
198            out[i + 4] = carry;
199            i += 1;
200        }
201        Self::from_limbs(out)
202    }
203
204    /// The exact 512-bit product of two `U256`s, as `(low, high)`.
205    ///
206    /// Halves are ordered low-first, matching `u128::widening_mul`.
207    /// OpenZeppelin's `Math.mul512` returns them the other way round.
208    ///
209    /// Cannot overflow, so no check and no `Option`; sixty-four `mul`/`mulhu`
210    /// pairs. This is the intermediate the full-precision
211    /// [`mul_div`](U256::mul_div) family divides back down -- reach for that
212    /// rather than for the halves, unless the 512-bit value is the answer.
213    #[cfg_attr(target_arch = "riscv32", inline(never))]
214    pub const fn widening_mul(self, rhs: Self) -> (Self, Self) {
215        let mut out = [0u32; 2 * LIMBS];
216        let mut i = 0;
217        while i < LIMBS {
218            let mut carry = 0u32;
219            let mut j = 0;
220            while j < LIMBS {
221                let (lo, hi) =
222                    mac(out[i + j], self.limbs[i], rhs.limbs[j], carry);
223                out[i + j] = lo;
224                carry = hi;
225                j += 1;
226            }
227            // Untouched by every earlier `i`, whose inner loop stopped one
228            // limb short of it -- so this is an assignment, not an add.
229            out[i + LIMBS] = carry;
230            i += 1;
231        }
232
233        let mut lo = [0u32; LIMBS];
234        let mut hi = [0u32; LIMBS];
235        let mut i = 0;
236        while i < LIMBS {
237            lo[i] = out[i];
238            hi[i] = out[i + LIMBS];
239            i += 1;
240        }
241        (Self::from_limbs(lo), Self::from_limbs(hi))
242    }
243
244    /// Multiplication by a 32-bit value, with a flag reporting overflow.
245    ///
246    /// Separate from the general 256x256 multiply so scaling by a small
247    /// constant does not drag the schoolbook loop in.
248    #[inline]
249    pub const fn overflowing_mul_u32(self, rhs: u32) -> (Self, bool) {
250        let mut out = [0u32; LIMBS];
251        let mut carry = 0u32;
252        let mut i = 0;
253        while i < LIMBS {
254            let (lo, hi) = mac(0, self.limbs[i], rhs, carry);
255            out[i] = lo;
256            carry = hi;
257            i += 1;
258        }
259        (Self::from_limbs(out), carry != 0)
260    }
261
262    /// Wrapping multiplication, with a flag reporting whether it wrapped.
263    ///
264    /// Outlined on rv32; see [`U256::widening_mul_u128`] and the note at the top of
265    /// this module.
266    #[cfg_attr(target_arch = "riscv32", inline(never))]
267    pub const fn overflowing_mul(self, rhs: Self) -> (Self, bool) {
268        let mut out = [0u32; LIMBS];
269        let mut overflow = false;
270
271        let mut i = 0;
272        while i < LIMBS {
273            let mut carry = 0u32;
274            let mut j = 0;
275            // Products landing inside the low 256 bits.
276            while j < LIMBS - i {
277                let (lo, hi) =
278                    mac(out[i + j], self.limbs[i], rhs.limbs[j], carry);
279                out[i + j] = lo;
280                carry = hi;
281                j += 1;
282            }
283            // A carry out of the top limb is a lost bit.
284            if carry != 0 {
285                overflow = true;
286            }
287            // So is any product whose position is past the top limb.
288            while j < LIMBS {
289                if self.limbs[i] != 0 && rhs.limbs[j] != 0 {
290                    overflow = true;
291                }
292                j += 1;
293            }
294            i += 1;
295        }
296
297        (Self::from_limbs(out), overflow)
298    }
299
300    /// Multiplication, returning `None` on overflow.
301    #[inline]
302    pub const fn checked_mul(self, rhs: Self) -> Option<Self> {
303        match self.overflowing_mul(rhs) {
304            (v, false) => Some(v),
305            (_, true) => None,
306        }
307    }
308
309    /// Multiplication, wrapping on overflow.
310    #[inline]
311    pub const fn wrapping_mul(self, rhs: Self) -> Self {
312        self.overflowing_mul(rhs).0
313    }
314
315    /// Multiplication, saturating at [`U256::MAX`].
316    #[inline]
317    pub const fn saturating_mul(self, rhs: Self) -> Self {
318        match self.overflowing_mul(rhs) {
319            (v, false) => v,
320            (_, true) => Self::MAX,
321        }
322    }
323
324    // -----------------------------------------------------------------------
325    // Bitwise, from `const fn`
326    //
327    // The operator impls below are the spelling callers want; they just are
328    // not `const`, and much of this crate is. Same answers.
329    // -----------------------------------------------------------------------
330
331    /// Bitwise AND, usable from a `const fn`.
332    #[inline]
333    pub const fn const_bitand(self, rhs: Self) -> Self {
334        let mut out = [0u32; LIMBS];
335        let mut i = 0;
336        while i < LIMBS {
337            out[i] = self.limbs[i] & rhs.limbs[i];
338            i += 1;
339        }
340        Self::from_limbs(out)
341    }
342
343    /// Bitwise OR, usable from a `const fn`.
344    #[inline]
345    pub const fn const_bitor(self, rhs: Self) -> Self {
346        let mut out = [0u32; LIMBS];
347        let mut i = 0;
348        while i < LIMBS {
349            out[i] = self.limbs[i] | rhs.limbs[i];
350            i += 1;
351        }
352        Self::from_limbs(out)
353    }
354
355    /// Bitwise XOR, usable from a `const fn`.
356    #[inline]
357    pub const fn const_bitxor(self, rhs: Self) -> Self {
358        let mut out = [0u32; LIMBS];
359        let mut i = 0;
360        while i < LIMBS {
361            out[i] = self.limbs[i] ^ rhs.limbs[i];
362            i += 1;
363        }
364        Self::from_limbs(out)
365    }
366
367    /// Bitwise NOT, usable from a `const fn`.
368    #[inline]
369    pub const fn const_not(self) -> Self {
370        let mut out = [0u32; LIMBS];
371        let mut i = 0;
372        while i < LIMBS {
373            out[i] = !self.limbs[i];
374            i += 1;
375        }
376        Self::from_limbs(out)
377    }
378
379    // -----------------------------------------------------------------------
380    // Shifts
381    // -----------------------------------------------------------------------
382
383    /// Left shift, masking the shift amount to `0..256` and reporting whether
384    /// the mask changed it. Mirrors `u128::overflowing_shl`.
385    #[inline]
386    pub const fn overflowing_shl(self, n: u32) -> (Self, bool) {
387        (self.wrapping_shl(n), n >= Self::BITS)
388    }
389
390    /// Left shift, masking the shift amount to `0..256`.
391    #[inline]
392    pub const fn wrapping_shl(self, n: u32) -> Self {
393        let n = n & (Self::BITS - 1);
394        let limb_shift = (n / 32) as usize;
395        let bit_shift = n % 32;
396
397        let mut out = [0u32; LIMBS];
398        let mut i = LIMBS;
399        while i > limb_shift {
400            i -= 1;
401            let src = i - limb_shift;
402            let mut v = self.limbs[src] << bit_shift;
403            if bit_shift > 0 && src > 0 {
404                v |= self.limbs[src - 1] >> (32 - bit_shift);
405            }
406            out[i] = v;
407        }
408        Self::from_limbs(out)
409    }
410
411    /// Left shift, returning `None` if the shift amount is at least 256.
412    #[inline]
413    pub const fn checked_shl(self, n: u32) -> Option<Self> {
414        if n >= Self::BITS {
415            None
416        } else {
417            Some(self.wrapping_shl(n))
418        }
419    }
420
421    /// Right shift, masking the shift amount to `0..256` and reporting whether
422    /// the mask changed it. Mirrors `u128::overflowing_shr`.
423    #[inline]
424    pub const fn overflowing_shr(self, n: u32) -> (Self, bool) {
425        (self.wrapping_shr(n), n >= Self::BITS)
426    }
427
428    /// Right shift, masking the shift amount to `0..256`.
429    #[inline]
430    pub const fn wrapping_shr(self, n: u32) -> Self {
431        let n = n & (Self::BITS - 1);
432        let limb_shift = (n / 32) as usize;
433        let bit_shift = n % 32;
434
435        let mut out = [0u32; LIMBS];
436        let mut i = 0;
437        while i + limb_shift < LIMBS {
438            let src = i + limb_shift;
439            let mut v = self.limbs[src] >> bit_shift;
440            if bit_shift > 0 && src + 1 < LIMBS {
441                v |= self.limbs[src + 1] << (32 - bit_shift);
442            }
443            out[i] = v;
444            i += 1;
445        }
446        Self::from_limbs(out)
447    }
448
449    /// Right shift, returning `None` if the shift amount is at least 256.
450    #[inline]
451    pub const fn checked_shr(self, n: u32) -> Option<Self> {
452        if n >= Self::BITS {
453            None
454        } else {
455            Some(self.wrapping_shr(n))
456        }
457    }
458}
459
460// ---------------------------------------------------------------------------
461// Operators
462//
463// `OVERFLOW_CHECKS` is a compile-time bool, so in a release contract build
464// (`overflow-checks = false`) each guard folds to `false` and the panic is
465// eliminated along with its message.
466// ---------------------------------------------------------------------------
467
468// With the `nightly` feature this is the same flag rustc keys the primitives'
469// operators off. The stable fallback is unconditionally `true`: stable
470// consumers are host tools, never gas-metered contracts, so they pay the
471// guard rather than ever silently wrapping (see the gate in lib.rs).
472#[cfg(feature = "nightly")]
473pub(crate) const OVERFLOW_CHECKS: bool = cfg!(overflow_checks);
474#[cfg(not(feature = "nightly"))]
475pub(crate) const OVERFLOW_CHECKS: bool = true;
476
477impl Add for U256 {
478    type Output = Self;
479
480    #[inline]
481    fn add(self, rhs: Self) -> Self {
482        let (v, overflow) = self.overflowing_add(rhs);
483        if OVERFLOW_CHECKS && overflow {
484            panic!("attempt to add with overflow");
485        }
486        v
487    }
488}
489
490impl Sub for U256 {
491    type Output = Self;
492
493    #[inline]
494    fn sub(self, rhs: Self) -> Self {
495        let (v, overflow) = self.overflowing_sub(rhs);
496        if OVERFLOW_CHECKS && overflow {
497            panic!("attempt to subtract with overflow");
498        }
499        v
500    }
501}
502
503impl Mul for U256 {
504    type Output = Self;
505
506    #[inline]
507    fn mul(self, rhs: Self) -> Self {
508        let (v, overflow) = self.overflowing_mul(rhs);
509        if OVERFLOW_CHECKS && overflow {
510            panic!("attempt to multiply with overflow");
511        }
512        v
513    }
514}
515
516impl Mul<u32> for U256 {
517    type Output = Self;
518
519    #[inline]
520    fn mul(self, rhs: u32) -> Self {
521        let (v, overflow) = self.overflowing_mul_u32(rhs);
522        if OVERFLOW_CHECKS && overflow {
523            panic!("attempt to multiply with overflow");
524        }
525        v
526    }
527}
528
529impl Div for U256 {
530    type Output = Self;
531
532    /// Division. Panics on a zero divisor regardless of `overflow-checks`,
533    /// matching the primitive integers.
534    #[inline]
535    fn div(self, rhs: Self) -> Self {
536        self.div_rem(rhs).0
537    }
538}
539
540impl Rem for U256 {
541    type Output = Self;
542
543    /// Remainder. Panics on a zero divisor regardless of `overflow-checks`,
544    /// matching the primitive integers.
545    #[inline]
546    fn rem(self, rhs: Self) -> Self {
547        self.div_rem(rhs).1
548    }
549}
550
551/// Splits a shift amount, already widened to `u128`, into the amount the shift
552/// primitives take and whether it was out of range.
553///
554/// Masking, not saturating. The primitives reduce an out-of-range shift modulo
555/// the width, so the amount that survives is the low `log2(BITS)` bits of the
556/// operand: `1u64 << 32` shifts by `(1 << 32) % 256 == 0`, and `-2` shifts by
557/// 254. Collapsing those to `u32::MAX` would shift by 255 in both cases, which
558/// is wrong in exactly the profiles that matter -- with `overflow-checks` off,
559/// the mask is the whole behaviour and nothing panics to hide it.
560///
561/// Callers widen with `as`, which sign-extends, so a negative amount arrives
562/// here as a very large `u128`: out of range for the panic path, and masking to
563/// its own low bits for the wrapping path, both matching the primitives.
564#[inline]
565pub(crate) const fn shift_bits(wide: u128) -> (u32, bool) {
566    ((wide as u32) & (U256::BITS - 1), wide >= U256::BITS as u128)
567}
568
569// Defined before `impl_shift!`, which expands it: `macro_rules!` scoping is
570// textual, not item-order-independent. The invocations for the non-shift
571// operators stay with the bitwise impls further down.
572macro_rules! impl_assign {
573    ($trait:ident, $method:ident, $base:ident, $base_method:ident, $rhs:ty) => {
574        impl $trait<$rhs> for U256 {
575            #[inline]
576            fn $method(&mut self, rhs: $rhs) {
577                *self = <Self as $base<$rhs>>::$base_method(*self, rhs);
578            }
579        }
580    };
581}
582
583/// Shifts are implemented for every integer type, exactly as the primitives do
584/// it.
585///
586/// The full family is load-bearing, not a convenience: with a single
587/// `Shl<u32>` impl an unsuffixed `x << 112` resolves the literal to `u32` and
588/// builds, but as soon as a second impl exists the literal becomes ambiguous
589/// and falls back to `i32`. Omitting the signed types here would break every
590/// bare-literal shift in the wild.
591macro_rules! impl_shift {
592    ($($t:ty),* $(,)?) => {
593        $(
594            impl Shl<$t> for U256 {
595                type Output = Self;
596
597                #[inline]
598                fn shl(self, rhs: $t) -> Self {
599                    let (bits, overflow) = shift_bits(rhs as u128);
600                    if OVERFLOW_CHECKS && overflow {
601                        panic!("attempt to shift left with overflow");
602                    }
603                    self.wrapping_shl(bits)
604                }
605            }
606
607            impl Shr<$t> for U256 {
608                type Output = Self;
609
610                #[inline]
611                fn shr(self, rhs: $t) -> Self {
612                    let (bits, overflow) = shift_bits(rhs as u128);
613                    if OVERFLOW_CHECKS && overflow {
614                        panic!("attempt to shift right with overflow");
615                    }
616                    self.wrapping_shr(bits)
617                }
618            }
619
620            impl_assign!(ShlAssign, shl_assign, Shl, shl, $t);
621            impl_assign!(ShrAssign, shr_assign, Shr, shr, $t);
622        )*
623    };
624}
625
626impl_shift!(
627    u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
628);
629
630impl Not for U256 {
631    type Output = Self;
632
633    #[inline]
634    fn not(self) -> Self {
635        self.const_not()
636    }
637}
638
639macro_rules! impl_bitop {
640    ($trait:ident, $method:ident, $const_method:ident) => {
641        impl $trait for U256 {
642            type Output = Self;
643
644            #[inline]
645            fn $method(self, rhs: Self) -> Self {
646                self.$const_method(rhs)
647            }
648        }
649    };
650}
651
652impl_bitop!(BitAnd, bitand, const_bitand);
653impl_bitop!(BitOr, bitor, const_bitor);
654impl_bitop!(BitXor, bitxor, const_bitxor);
655
656// `impl_assign!` itself is defined above `impl_shift!`, which also expands it.
657impl_assign!(AddAssign, add_assign, Add, add, U256);
658impl_assign!(SubAssign, sub_assign, Sub, sub, U256);
659impl_assign!(MulAssign, mul_assign, Mul, mul, U256);
660impl_assign!(MulAssign, mul_assign, Mul, mul, u32);
661impl_assign!(DivAssign, div_assign, Div, div, U256);
662impl_assign!(RemAssign, rem_assign, Rem, rem, U256);
663impl_assign!(BitAndAssign, bitand_assign, BitAnd, bitand, U256);
664impl_assign!(BitOrAssign, bitor_assign, BitOr, bitor, U256);
665impl_assign!(BitXorAssign, bitxor_assign, BitXor, bitxor, U256);
666
667#[cfg(test)]
668mod tests {
669    use super::shift_bits;
670
671    /// The operator-level version of this can only be tested in one profile at
672    /// a time -- with `overflow-checks` on every out-of-range shift panics
673    /// before the mask is observable, and that is the profile tests run in.
674    /// The mask is the entire behaviour of a release contract build, so it is
675    /// pinned here instead.
676    #[test]
677    fn out_of_range_shift_amounts_mask_rather_than_saturate() {
678        assert_eq!(shift_bits(0), (0, false));
679        assert_eq!(shift_bits(255), (255, false));
680        assert_eq!(shift_bits(256), (0, true));
681        // 300 % 256; saturating to `u32::MAX` would have said 255.
682        assert_eq!(shift_bits(300), (44, true));
683        // Wider than `u32`: the low bits survive the narrowing.
684        assert_eq!(shift_bits(1u128 << 32), (0, true));
685        assert_eq!(shift_bits((1u128 << 32) + 7), (7, true));
686    }
687
688    /// Callers widen with `as`, so negatives arrive sign-extended.
689    #[test]
690    fn negative_shift_amounts_mask_to_their_low_bits() {
691        assert_eq!(shift_bits(-1i32 as u128), (255, true));
692        // Saturating gave 255 here too, which is the bug: -2 is 254.
693        assert_eq!(shift_bits(-2i32 as u128), (254, true));
694        assert_eq!(shift_bits(-256i64 as u128), (0, true));
695    }
696}