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}