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}