Skip to main content

const_num_traits/ops/
bits.rs

1//! Bit-manipulation operations beyond `PrimInt`: unbounded and funnel
2//! shifts, exact (lossless) shifts, bit isolation/indexing, bit width and
3//! PDEP/PEXT-style bit deposit/extract, mirroring the corresponding inherent
4//! methods on the primitive integer types.
5//!
6//! Stability in std (as of nightly 2026): `unbounded_shl`/`unbounded_shr`
7//! are stable since 1.87; `highest_one`/`lowest_one`/`isolate_highest_one`/
8//! `isolate_lowest_one`/`bit_width` since 1.98; funnel shifts, exact shifts
9//! and `deposit_bits`/`extract_bits` are still nightly-only. Everything
10//! newer than the crate's MSRV is hand-rolled with the same semantics.
11//!
12//! **CT tiers**: [`IsolateHighestOne`]/[`IsolateLowestOne`], [`BitWidth`] and
13//! the funnel/unbounded shifts (under the public-parameter convention for shift
14//! amounts) are Tier A. [`DepositBits`]/[`ExtractBits`] are branchless on the
15//! *operand* but this portable fallback's loop count is `popcount(mask)`, so
16//! they are Tier A only when the **mask is public** (it is the analogue of a
17//! shift amount); for a secret mask they are Tier C. [`HighestOne`]/[`LowestOne`]
18//! and [`ShlExact`]/[`ShrExact`] are Tier B (`Option` returns).
19
20c0nst::c0nst! {
21/// Performs a left shift that never panics, returning 0 for large shifts.
22pub c0nst trait UnboundedShl: Sized {
23    /// The result type (`Self` for the primitive impls).
24    type Output;
25    /// Unbounded shift left. Computes `self << rhs`, without bounding the
26    /// value of `rhs`: if `rhs >= BITS` the entire value is shifted out and
27    /// 0 is returned.
28    ///
29    /// ```
30    /// use const_num_traits::UnboundedShl;
31    ///
32    /// assert_eq!(UnboundedShl::unbounded_shl(1u8, 4), 16);
33    /// assert_eq!(UnboundedShl::unbounded_shl(1u8, 200), 0);
34    /// ```
35    fn unbounded_shl(self, rhs: u32) -> Self::Output;
36}
37}
38
39c0nst::c0nst! {
40/// Performs a right shift that never panics, shifting in zero or sign bits
41/// for large shift amounts.
42pub c0nst trait UnboundedShr: Sized {
43    /// The result type (`Self` for the primitive impls).
44    type Output;
45    /// Unbounded shift right. Computes `self >> rhs`, without bounding the
46    /// value of `rhs`: if `rhs >= BITS`, unsigned values become 0 and signed
47    /// values become 0 or -1 depending on the sign (the sign bit fills every
48    /// position).
49    ///
50    /// ```
51    /// use const_num_traits::UnboundedShr;
52    ///
53    /// assert_eq!(UnboundedShr::unbounded_shr(16u8, 4), 1);
54    /// assert_eq!(UnboundedShr::unbounded_shr(16u8, 200), 0);
55    /// assert_eq!(UnboundedShr::unbounded_shr(-16i8, 200), -1);
56    /// ```
57    fn unbounded_shr(self, rhs: u32) -> Self::Output;
58}
59}
60
61macro_rules! unbounded_shift_impl {
62    (unsigned $($t:ty)*) => {$(
63        c0nst::c0nst! {
64        c0nst impl UnboundedShl for $t {
65            type Output = $t;
66            #[inline]
67            fn unbounded_shl(self, rhs: u32) -> Self {
68                if rhs < <$t>::BITS { self << rhs } else { 0 }
69            }
70        }
71        }
72        c0nst::c0nst! {
73        c0nst impl UnboundedShr for $t {
74            type Output = $t;
75            #[inline]
76            fn unbounded_shr(self, rhs: u32) -> Self {
77                if rhs < <$t>::BITS { self >> rhs } else { 0 }
78            }
79        }
80        }
81    )*};
82    (signed $($t:ty)*) => {$(
83        c0nst::c0nst! {
84        c0nst impl UnboundedShl for $t {
85            type Output = $t;
86            #[inline]
87            fn unbounded_shl(self, rhs: u32) -> Self {
88                if rhs < <$t>::BITS { self << rhs } else { 0 }
89            }
90        }
91        }
92        c0nst::c0nst! {
93        c0nst impl UnboundedShr for $t {
94            type Output = $t;
95            #[inline]
96            fn unbounded_shr(self, rhs: u32) -> Self {
97                if rhs < <$t>::BITS {
98                    self >> rhs
99                } else {
100                    // shifting by BITS-1 copies the sign bit everywhere
101                    self >> (<$t>::BITS - 1)
102                }
103            }
104        }
105        }
106    )*};
107}
108
109unbounded_shift_impl!(unsigned usize u8 u16 u32 u64 u128);
110unbounded_shift_impl!(signed isize i8 i16 i32 i64 i128);
111
112c0nst::c0nst! {
113/// Performs a funnel left shift on a double-width value formed from two words.
114pub c0nst trait FunnelShl: Sized {
115    /// Funnel shift left: concatenates `self` (high word) with `rhs` (low
116    /// word), shifts the combination left by `n`, and returns the high word
117    /// — i.e. `(self << n) | (rhs >> (BITS - n))`. Like std, this is only
118    /// provided for unsigned types.
119    ///
120    /// # Panics
121    ///
122    /// Panics if `n >= BITS`.
123    ///
124    /// ```
125    /// use const_num_traits::FunnelShl;
126    ///
127    /// assert_eq!(FunnelShl::funnel_shl(0x01u8, 0x80, 1), 0x03);
128    /// ```
129    type Output;
130    fn funnel_shl(self, rhs: Self, n: u32) -> Self::Output;
131}
132}
133
134c0nst::c0nst! {
135/// Performs a funnel right shift on a double-width value formed from two words.
136pub c0nst trait FunnelShr: Sized {
137    /// Funnel shift right: concatenates `self` (high word) with `rhs` (low
138    /// word), shifts the combination right by `n`, and returns the low word
139    /// — i.e. `(rhs >> n) | (self << (BITS - n))`. Like std, this is only
140    /// provided for unsigned types.
141    ///
142    /// # Panics
143    ///
144    /// Panics if `n >= BITS`.
145    ///
146    /// ```
147    /// use const_num_traits::FunnelShr;
148    ///
149    /// assert_eq!(FunnelShr::funnel_shr(0x01u8, 0x80, 1), 0xC0);
150    /// ```
151    type Output;
152    fn funnel_shr(self, rhs: Self, n: u32) -> Self::Output;
153}
154}
155
156macro_rules! funnel_shift_impl {
157    ($($t:ty)*) => {$(
158        c0nst::c0nst! {
159        c0nst impl FunnelShl for $t {
160            type Output = $t;
161            #[inline]
162            #[track_caller]
163            fn funnel_shl(self, rhs: Self, n: u32) -> Self {
164                assert!(n < <$t>::BITS, "attempt to funnel shift left with overflow");
165                if n == 0 {
166                    self
167                } else {
168                    (self << n) | (rhs >> (<$t>::BITS - n))
169                }
170            }
171        }
172        }
173        c0nst::c0nst! {
174        c0nst impl FunnelShr for $t {
175            type Output = $t;
176            #[inline]
177            #[track_caller]
178            fn funnel_shr(self, rhs: Self, n: u32) -> Self {
179                assert!(n < <$t>::BITS, "attempt to funnel shift right with overflow");
180                if n == 0 {
181                    rhs
182                } else {
183                    (rhs >> n) | (self << (<$t>::BITS - n))
184                }
185            }
186        }
187        }
188    )*};
189}
190
191funnel_shift_impl!(usize u8 u16 u32 u64 u128);
192
193c0nst::c0nst! {
194/// Performs a lossless (exactly reversible) left shift.
195pub c0nst trait ShlExact: Sized {
196    /// The result type (`Self` for the primitive impls).
197    type Output;
198    /// Exact shift left. Computes `self << rhs` if no bits would be shifted
199    /// out (so the operation can be losslessly reversed), `None` otherwise.
200    ///
201    /// ```
202    /// use const_num_traits::ShlExact;
203    ///
204    /// assert_eq!(ShlExact::shl_exact(0x11u8, 3), Some(0x88));
205    /// assert_eq!(ShlExact::shl_exact(0x11u8, 4), None);
206    /// ```
207    fn shl_exact(self, rhs: u32) -> Option<Self::Output>;
208}
209}
210
211c0nst::c0nst! {
212/// Performs a lossless (exactly reversible) right shift.
213pub c0nst trait ShrExact: Sized {
214    /// The result type (`Self` for the primitive impls).
215    type Output;
216    /// Exact shift right. Computes `self >> rhs` if no one-bits would be
217    /// shifted out (so the operation can be losslessly reversed), `None`
218    /// otherwise.
219    ///
220    /// ```
221    /// use const_num_traits::ShrExact;
222    ///
223    /// assert_eq!(ShrExact::shr_exact(0x88u8, 3), Some(0x11));
224    /// assert_eq!(ShrExact::shr_exact(0x88u8, 4), None);
225    /// ```
226    fn shr_exact(self, rhs: u32) -> Option<Self::Output>;
227}
228}
229
230macro_rules! exact_shift_impl {
231    (unsigned $($t:ty)*) => {$(
232        c0nst::c0nst! {
233        c0nst impl ShlExact for $t {
234            type Output = $t;
235            #[inline]
236            fn shl_exact(self, rhs: u32) -> Option<Self> {
237                if rhs <= <$t>::leading_zeros(self) && rhs < <$t>::BITS {
238                    Some(self << rhs)
239                } else {
240                    None
241                }
242            }
243        }
244        }
245        exact_shift_impl!(@shr $t);
246    )*};
247    (signed $($t:ty)*) => {$(
248        c0nst::c0nst! {
249        c0nst impl ShlExact for $t {
250            type Output = $t;
251            #[inline]
252            fn shl_exact(self, rhs: u32) -> Option<Self> {
253                // for negative values the sign-extension bits are the
254                // recoverable ones, hence leading_ones
255                if rhs < <$t>::leading_zeros(self) || rhs < <$t>::leading_ones(self) {
256                    Some(self << rhs)
257                } else {
258                    None
259                }
260            }
261        }
262        }
263        exact_shift_impl!(@shr $t);
264    )*};
265    (@shr $t:ty) => {
266        c0nst::c0nst! {
267        c0nst impl ShrExact for $t {
268            type Output = $t;
269            #[inline]
270            fn shr_exact(self, rhs: u32) -> Option<Self> {
271                if rhs <= <$t>::trailing_zeros(self) && rhs < <$t>::BITS {
272                    Some(self >> rhs)
273                } else {
274                    None
275                }
276            }
277        }
278        }
279    };
280}
281
282exact_shift_impl!(unsigned usize u8 u16 u32 u64 u128);
283exact_shift_impl!(signed isize i8 i16 i32 i64 i128);
284
285c0nst::c0nst! {
286/// Finds the index of the highest one-bit.
287pub c0nst trait HighestOne: Sized {
288    /// Returns the index of the highest bit set to one, or `None` if the
289    /// value is zero.
290    ///
291    /// ```
292    /// use const_num_traits::HighestOne;
293    ///
294    /// assert_eq!(HighestOne::highest_one(0b0101_0000u8), Some(6));
295    /// assert_eq!(HighestOne::highest_one(0u8), None);
296    /// ```
297    fn highest_one(self) -> Option<u32>;
298}
299}
300
301c0nst::c0nst! {
302/// Finds the index of the lowest one-bit.
303pub c0nst trait LowestOne: Sized {
304    /// Returns the index of the lowest bit set to one, or `None` if the
305    /// value is zero.
306    ///
307    /// ```
308    /// use const_num_traits::LowestOne;
309    ///
310    /// assert_eq!(LowestOne::lowest_one(0b0101_0000u8), Some(4));
311    /// assert_eq!(LowestOne::lowest_one(0u8), None);
312    /// ```
313    fn lowest_one(self) -> Option<u32>;
314}
315}
316
317c0nst::c0nst! {
318/// Isolates the highest one-bit, branchlessly.
319pub c0nst trait IsolateHighestOne: Sized {
320    /// Returns `self` with only its highest one-bit kept, or 0 if the value
321    /// is zero.
322    ///
323    /// ```
324    /// use const_num_traits::IsolateHighestOne;
325    ///
326    /// assert_eq!(IsolateHighestOne::isolate_highest_one(0b0101_0000u8), 0b0100_0000);
327    /// ```
328    type Output;
329    fn isolate_highest_one(self) -> Self::Output;
330}
331}
332
333c0nst::c0nst! {
334/// Isolates the lowest one-bit, branchlessly.
335pub c0nst trait IsolateLowestOne: Sized {
336    /// Returns `self` with only its lowest one-bit kept, or 0 if the value
337    /// is zero.
338    ///
339    /// ```
340    /// use const_num_traits::IsolateLowestOne;
341    ///
342    /// assert_eq!(IsolateLowestOne::isolate_lowest_one(0b0101_0000u8), 0b0001_0000);
343    /// ```
344    type Output;
345    fn isolate_lowest_one(self) -> Self::Output;
346}
347}
348
349macro_rules! isolate_one_impl {
350    // operate on the unsigned bit pattern; `$u` is `$t` itself for the
351    // unsigned instantiations
352    ($($t:ty => $u:ty;)*) => {$(
353        c0nst::c0nst! {
354        c0nst impl HighestOne for $t {
355            #[inline]
356            fn highest_one(self) -> Option<u32> {
357                if self == 0 {
358                    None
359                } else {
360                    Some(<$t>::BITS - 1 - <$t>::leading_zeros(self))
361                }
362            }
363        }
364        }
365
366        c0nst::c0nst! {
367        c0nst impl LowestOne for $t {
368            #[inline]
369            fn lowest_one(self) -> Option<u32> {
370                if self == 0 {
371                    None
372                } else {
373                    Some(<$t>::trailing_zeros(self))
374                }
375            }
376        }
377        }
378
379        c0nst::c0nst! {
380        c0nst impl IsolateHighestOne for $t {
381            type Output = $t;
382            #[inline]
383            fn isolate_highest_one(self) -> Self {
384                let bits = self as $u;
385                (bits & ((1 as $u) << (<$u>::BITS - 1)).wrapping_shr(<$u>::leading_zeros(bits))) as $t
386            }
387        }
388        }
389
390        c0nst::c0nst! {
391        c0nst impl IsolateLowestOne for $t {
392            type Output = $t;
393            #[inline]
394            fn isolate_lowest_one(self) -> Self {
395                self & <$t>::wrapping_neg(self)
396            }
397        }
398        }
399    )*};
400}
401
402isolate_one_impl! {
403    u8 => u8; u16 => u16; u32 => u32; u64 => u64; usize => usize; u128 => u128;
404    i8 => u8; i16 => u16; i32 => u32; i64 => u64; isize => usize; i128 => u128;
405}
406
407c0nst::c0nst! {
408/// Computes the minimal number of bits required to represent an unsigned value.
409pub c0nst trait BitWidth: Sized {
410    /// Returns the minimum number of bits required to represent `self`,
411    /// i.e. `BITS - leading_zeros`. Returns 0 for 0. Like std, this is only
412    /// provided for unsigned types.
413    ///
414    /// ```
415    /// use const_num_traits::BitWidth;
416    ///
417    /// assert_eq!(BitWidth::bit_width(0u8), 0);
418    /// assert_eq!(BitWidth::bit_width(0b0101_0000u8), 7);
419    /// ```
420    fn bit_width(self) -> u32;
421}
422}
423
424macro_rules! bit_width_impl {
425    ($($t:ty)*) => {$(
426        c0nst::c0nst! {
427        c0nst impl BitWidth for $t {
428            #[inline]
429            fn bit_width(self) -> u32 {
430                <$t>::BITS - <$t>::leading_zeros(self)
431            }
432        }
433        }
434    )*};
435}
436
437bit_width_impl!(usize u8 u16 u32 u64 u128);
438
439c0nst::c0nst! {
440/// A value's operating **width** in bits — how many bits it is represented over,
441/// possibly **less than its storage capacity**. Fixed per type for a fixed-width
442/// carrier (`u32` → 32, `arbitrary-int`'s `u48` → 48), per-value for a
443/// variable-width bignum. Named after `crypto-bigint`'s `BoxedUint::bits_precision`.
444/// Contrast [`BitWidth::bit_width`] (*bit-length* — significant bits,
445/// `<= bits_precision()`).
446///
447/// Binary carriers only — not decimals/floats/rationals (non-binary precision).
448///
449/// **Footgun:** on a variable-width carrier the identity is minimal-width, so
450/// `bits_precision(&Zero::zero())` is `0`, not the operating width — probe a
451/// full-width witness (the modulus), never the identity. See [`WithPrecision`].
452///
453/// `&self`, not `self`: a width query never consumes its value (cf.
454/// [`FromBytes`](crate::FromBytes)), and borrowing lets a non-`Copy` carrier be a
455/// witness without a clone — how [`WithPrecision`]'s `_of` forms stay `Copy`-free.
456pub c0nst trait BitsPrecision: Sized {
457    /// The number of bits `self` operates over (its constructed width).
458    ///
459    /// ```
460    /// use const_num_traits::BitsPrecision;
461    ///
462    /// assert_eq!(BitsPrecision::bits_precision(&0u32), 32);
463    /// assert_eq!(BitsPrecision::bits_precision(&u32::MAX), 32);
464    /// assert_eq!(BitsPrecision::bits_precision(&7u8), 8);
465    /// ```
466    fn bits_precision(&self) -> u32;
467}
468}
469
470macro_rules! bits_precision_impl {
471    ($($t:ty)*) => {$(
472        c0nst::c0nst! {
473        c0nst impl BitsPrecision for $t {
474            #[inline]
475            fn bits_precision(&self) -> u32 {
476                <$t>::BITS
477            }
478        }
479        }
480    )*};
481}
482
483bits_precision_impl!(usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128);
484
485c0nst::c0nst! {
486/// Establishes a value's operating **width** from a witness — the constructive
487/// companion to [`BitsPrecision`]. Method names mirror `crypto-bigint`'s
488/// `BoxedUint::{zero_with_precision, one_with_precision, widen}`.
489///
490/// **Why it exists:** [`Zero::zero`](crate::Zero::zero)/[`One::one`](crate::One::one)
491/// are minimal-width on a runtime-width carrier, so a reducer seeded with `zero()`
492/// and grown toward a modulus width operates at the seed width and wraps early —
493/// correct on a fixed-width type, silently truncated on a variable-width one.
494/// `T::zero_with_precision_of(&m)` seeds at the modulus width instead.
495///
496/// [`widen_to_precision`](Self::widen_to_precision) grows, never shrinks, and
497/// preserves the value (identity on a fixed-width carrier). It preserves value
498/// only to a *representation-compatible* width — a fixed-point carrier's fractional
499/// format must match — which is why the `_of` forms take a witness *value*, not a
500/// bit count. Those forms borrow the witness, so they carry **no `Copy` bound** and
501/// serve `Clone`-only carriers, the case the family exists for.
502///
503/// ```
504/// use const_num_traits::WithPrecision;
505///
506/// // Fixed-width: width is the type; the requested precision is ignored.
507/// assert_eq!(WithPrecision::widen_to_precision(5u32, 256), 5u32);
508/// assert_eq!(WithPrecision::zero_with_precision_of(&99u32), 0u32);
509/// let one: u32 = WithPrecision::one_with_precision(128);
510/// assert_eq!(one, 1);
511/// ```
512pub c0nst trait WithPrecision: [c0nst] BitsPrecision {
513    /// Returns `self` re-represented over a width of **at least**
514    /// `bits_precision`, preserving its numeric value. Never shrinks; on a
515    /// fixed-width carrier the width is the type and this is the identity.
516    fn widen_to_precision(self, bits_precision: u32) -> Self;
517
518    /// A [`Zero`](crate::Zero) carried at a width of at least `bits_precision`.
519    #[inline]
520    fn zero_with_precision(bits_precision: u32) -> Self
521    where
522        Self: [c0nst] crate::Zero,
523    {
524        Self::zero().widen_to_precision(bits_precision)
525    }
526
527    /// A [`One`](crate::One) carried at a width of at least `bits_precision`.
528    #[inline]
529    fn one_with_precision(bits_precision: u32) -> Self
530    where
531        Self: [c0nst] crate::One,
532    {
533        Self::one().widen_to_precision(bits_precision)
534    }
535
536    /// [`widen_to_precision`](Self::widen_to_precision) to the width of a
537    /// `witness` value — typically the modulus a reducer operates over. Prefer
538    /// this to hand-picking a bit count: the witness carries the intended width.
539    #[inline]
540    fn widen_to_precision_of(self, witness: &Self) -> Self {
541        self.widen_to_precision(witness.bits_precision())
542    }
543
544    /// A [`Zero`](crate::Zero) carried at the `witness`'s width — the width-safe
545    /// seed for a generic accumulator.
546    #[inline]
547    fn zero_with_precision_of(witness: &Self) -> Self
548    where
549        Self: [c0nst] crate::Zero,
550    {
551        Self::zero_with_precision(witness.bits_precision())
552    }
553
554    /// A [`One`](crate::One) carried at the `witness`'s width.
555    #[inline]
556    fn one_with_precision_of(witness: &Self) -> Self
557    where
558        Self: [c0nst] crate::One,
559    {
560        Self::one_with_precision(witness.bits_precision())
561    }
562}
563}
564
565macro_rules! with_precision_impl {
566    ($($t:ty)*) => {$(
567        c0nst::c0nst! {
568        c0nst impl WithPrecision for $t {
569            #[inline]
570            fn widen_to_precision(self, _bits_precision: u32) -> $t {
571                self
572            }
573        }
574        }
575    )*};
576}
577
578with_precision_impl!(usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128);
579
580c0nst::c0nst! {
581/// Scatters bits through a mask (the PDEP operation).
582pub c0nst trait DepositBits: Sized {
583    /// Scatters the contiguous low-order bits of `self` into the positions
584    /// of the one-bits of `mask` (the PDEP operation). All other bits of the
585    /// result are zero. Like std, this is only provided for unsigned types.
586    ///
587    /// ```
588    /// use const_num_traits::DepositBits;
589    ///
590    /// assert_eq!(DepositBits::deposit_bits(0b101u8, 0b1111_0000), 0b0101_0000);
591    /// ```
592    type Output;
593    fn deposit_bits(self, mask: Self) -> Self::Output;
594}
595}
596
597c0nst::c0nst! {
598/// Gathers bits through a mask (the PEXT operation).
599pub c0nst trait ExtractBits: Sized {
600    /// Gathers the bits of `self` selected by the one-bits of `mask` into
601    /// the contiguous low-order bits of the result (the PEXT operation).
602    /// Like std, this is only provided for unsigned types.
603    ///
604    /// ```
605    /// use const_num_traits::ExtractBits;
606    ///
607    /// assert_eq!(ExtractBits::extract_bits(0b0101_0011u8, 0b1111_0000), 0b101);
608    /// ```
609    type Output;
610    fn extract_bits(self, mask: Self) -> Self::Output;
611}
612}
613
614macro_rules! deposit_extract_impl {
615    ($($t:ty)*) => {$(
616        c0nst::c0nst! {
617        c0nst impl DepositBits for $t {
618            type Output = $t;
619            #[inline]
620            fn deposit_bits(self, mask: Self) -> Self {
621                let mut result: $t = 0;
622                let mut remaining = mask;
623                let mut bb: $t = 1;
624                while remaining != 0 {
625                    let lowest = remaining & <$t>::wrapping_neg(remaining);
626                    // branchless on the operand: mask is all-ones iff bit `bb` of self is set
627                    let bit_mask = (((self & bb) != 0) as $t).wrapping_neg();
628                    result |= lowest & bit_mask;
629                    remaining &= remaining - 1;
630                    bb = <$t>::wrapping_shl(bb, 1);
631                }
632                result
633            }
634        }
635        }
636
637        c0nst::c0nst! {
638        c0nst impl ExtractBits for $t {
639            type Output = $t;
640            #[inline]
641            fn extract_bits(self, mask: Self) -> Self {
642                let mut result: $t = 0;
643                let mut remaining = mask;
644                let mut bb: $t = 1;
645                while remaining != 0 {
646                    let lowest = remaining & <$t>::wrapping_neg(remaining);
647                    // branchless on the operand: mask is all-ones iff bit `lowest` of self is set
648                    let bit_mask = (((self & lowest) != 0) as $t).wrapping_neg();
649                    result |= bb & bit_mask;
650                    remaining &= remaining - 1;
651                    bb = <$t>::wrapping_shl(bb, 1);
652                }
653                result
654            }
655        }
656        }
657    )*};
658}
659
660deposit_extract_impl!(usize u8 u16 u32 u64 u128);
661
662#[cfg(test)]
663mod tests {
664    use super::*;
665
666    #[test]
667    fn unbounded_shifts() {
668        assert_eq!(UnboundedShl::unbounded_shl(1u8, 7), 0x80);
669        assert_eq!(UnboundedShl::unbounded_shl(1u8, 8), 0);
670        assert_eq!(UnboundedShr::unbounded_shr(0x80u8, 8), 0);
671        assert_eq!(UnboundedShr::unbounded_shr(-1i8, 100), -1);
672        assert_eq!(UnboundedShr::unbounded_shr(i8::MAX, 100), 0);
673    }
674
675    #[test]
676    fn funnel_shifts() {
677        // 0x0180 << 1 = 0x0300 -> high byte 0x03
678        assert_eq!(FunnelShl::funnel_shl(0x01u8, 0x80, 1), 0x03);
679        assert_eq!(FunnelShl::funnel_shl(0xABu8, 0xCD, 0), 0xAB);
680        // 0x0180 >> 1 = 0x00C0 -> low byte 0xC0
681        assert_eq!(FunnelShr::funnel_shr(0x01u8, 0x80, 1), 0xC0);
682        assert_eq!(FunnelShr::funnel_shr(0xABu8, 0xCD, 0), 0xCD);
683        // rotation is a funnel shift with both words equal
684        assert_eq!(
685            FunnelShl::funnel_shl(0x81u8, 0x81, 1),
686            0x81u8.rotate_left(1)
687        );
688    }
689
690    #[test]
691    #[should_panic(expected = "attempt to funnel shift left with overflow")]
692    fn funnel_shl_panics() {
693        let _ = FunnelShl::funnel_shl(1u8, 1, 8);
694    }
695
696    #[test]
697    fn exact_shifts() {
698        assert_eq!(ShlExact::shl_exact(0x11u8, 3), Some(0x88));
699        assert_eq!(ShlExact::shl_exact(0x11u8, 4), None);
700        assert_eq!(ShrExact::shr_exact(0x88u8, 3), Some(0x11));
701        assert_eq!(ShrExact::shr_exact(0x88u8, 4), None);
702        assert_eq!(ShrExact::shr_exact(0u8, 7), Some(0));
703        assert_eq!(ShrExact::shr_exact(0u8, 8), None);
704        // negative values: sign bits are recoverable
705        assert_eq!(ShlExact::shl_exact(-1i8, 7), Some(i8::MIN));
706        assert_eq!(ShlExact::shl_exact(-2i8, 6), Some(i8::MIN));
707        assert_eq!(ShlExact::shl_exact(-2i8, 7), None);
708        assert_eq!(ShlExact::shl_exact(1i8, 6), Some(64));
709        assert_eq!(ShlExact::shl_exact(1i8, 7), None);
710    }
711
712    #[test]
713    fn isolate() {
714        assert_eq!(HighestOne::highest_one(0b0101_0000u8), Some(6));
715        assert_eq!(HighestOne::highest_one(0u8), None);
716        assert_eq!(LowestOne::lowest_one(0b0101_0000u8), Some(4));
717        assert_eq!(LowestOne::lowest_one(0i64), None);
718        assert_eq!(
719            IsolateHighestOne::isolate_highest_one(0b0101_0000u8),
720            0b0100_0000
721        );
722        assert_eq!(
723            IsolateLowestOne::isolate_lowest_one(0b0101_0000u8),
724            0b0001_0000
725        );
726        assert_eq!(IsolateHighestOne::isolate_highest_one(0u8), 0);
727        assert_eq!(IsolateLowestOne::isolate_lowest_one(0u8), 0);
728        // signed: operates on the bit pattern
729        assert_eq!(HighestOne::highest_one(-1i8), Some(7));
730        assert_eq!(IsolateHighestOne::isolate_highest_one(-1i8), i8::MIN);
731        assert_eq!(IsolateLowestOne::isolate_lowest_one(-2i8), 2);
732    }
733
734    #[test]
735    fn bit_width() {
736        assert_eq!(BitWidth::bit_width(0u8), 0);
737        assert_eq!(BitWidth::bit_width(1u8), 1);
738        assert_eq!(BitWidth::bit_width(255u8), 8);
739        assert_eq!(BitWidth::bit_width(0x0101u16), 9);
740    }
741
742    #[test]
743    fn with_precision_is_identity_on_fixed_width() {
744        // Fixed-width carriers: width is the type, requested precision ignored.
745        assert_eq!(WithPrecision::widen_to_precision(5u32, 256), 5);
746        assert_eq!(WithPrecision::widen_to_precision_of(5u32, &9999u32), 5);
747        assert_eq!(WithPrecision::zero_with_precision_of(&99u32), 0u32);
748        assert_eq!(WithPrecision::one_with_precision_of(&99u8), 1u8);
749        let z: u16 = WithPrecision::zero_with_precision(64);
750        let o: u16 = WithPrecision::one_with_precision(64);
751        assert_eq!((z, o), (0, 1));
752        // signed carriers: width is the type (`i32::BITS`), ops are the identity.
753        assert_eq!(BitsPrecision::bits_precision(&-1i32), 32);
754        assert_eq!(WithPrecision::widen_to_precision(-5i16, 128), -5);
755        assert_eq!(WithPrecision::zero_with_precision_of(&-9i64), 0i64);
756    }
757
758    // A runtime-width, non-`Copy` carrier (only `Clone`). Proves the witness `_of`
759    // forms carry no `Copy` bound and establish width from a borrowed witness.
760    #[derive(Clone, PartialEq, Debug)]
761    struct RtWidth {
762        val: u64,
763        width: u32,
764    }
765
766    impl crate::Zero for RtWidth {
767        fn zero() -> Self {
768            RtWidth { val: 0, width: 0 }
769        }
770        fn set_zero(&mut self) {
771            *self = <Self as crate::Zero>::zero();
772        }
773        fn is_zero(&self) -> bool {
774            self.val == 0
775        }
776    }
777    impl crate::One for RtWidth {
778        fn one() -> Self {
779            RtWidth { val: 1, width: 1 }
780        }
781        fn set_one(&mut self) {
782            *self = <Self as crate::One>::one();
783        }
784        fn is_one(&self) -> bool {
785            self.val == 1
786        }
787    }
788    impl BitsPrecision for RtWidth {
789        fn bits_precision(&self) -> u32 {
790            self.width
791        }
792    }
793    impl WithPrecision for RtWidth {
794        fn widen_to_precision(self, bits_precision: u32) -> Self {
795            RtWidth {
796                val: self.val,
797                width: self.width.max(bits_precision),
798            }
799        }
800    }
801
802    #[test]
803    fn with_precision_serves_non_copy_carrier() {
804        let modulus = RtWidth { val: 7, width: 256 };
805        // zero/one seeded at the witness width; `modulus` is only borrowed.
806        let z = RtWidth::zero_with_precision_of(&modulus);
807        let o = RtWidth::one_with_precision_of(&modulus);
808        assert_eq!(z, RtWidth { val: 0, width: 256 });
809        assert_eq!(o, RtWidth { val: 1, width: 256 });
810        // widen an owned value to the witness width; witness survives.
811        let widened = RtWidth { val: 3, width: 8 }.widen_to_precision_of(&modulus);
812        assert_eq!(widened, RtWidth { val: 3, width: 256 });
813        assert_eq!(modulus.width, 256); // witness not consumed
814    }
815
816    #[test]
817    fn deposit_extract() {
818        assert_eq!(DepositBits::deposit_bits(0b101u8, 0b1111_0000), 0b0101_0000);
819        assert_eq!(DepositBits::deposit_bits(0xFFu8, 0b1010_1010), 0b1010_1010);
820        assert_eq!(ExtractBits::extract_bits(0b0101_0011u8, 0b1111_0000), 0b101);
821        assert_eq!(ExtractBits::extract_bits(0xFFu8, 0b1010_1010), 0b1111);
822        // extract is the inverse of deposit on the masked bits
823        let mask = 0b0110_1001u8;
824        for x in 0u8..16 {
825            let deposited = DepositBits::deposit_bits(x, mask);
826            assert_eq!(deposited & !mask, 0);
827            assert_eq!(ExtractBits::extract_bits(deposited, mask), x);
828        }
829    }
830}