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
20use core::ops::{Shl, Shr};
21
22c0nst::c0nst! {
23/// Performs a left shift that never panics, returning 0 for large shifts.
24pub c0nst trait UnboundedShl: Sized + [c0nst] Shl<u32> {
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 as Shl<u32>>::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 + [c0nst] Shr<u32> {
43    /// Unbounded shift right. Computes `self >> rhs`, without bounding the
44    /// value of `rhs`: if `rhs >= BITS`, unsigned values become 0 and signed
45    /// values become 0 or -1 depending on the sign (the sign bit fills every
46    /// position).
47    ///
48    /// ```
49    /// use const_num_traits::UnboundedShr;
50    ///
51    /// assert_eq!(UnboundedShr::unbounded_shr(16u8, 4), 1);
52    /// assert_eq!(UnboundedShr::unbounded_shr(16u8, 200), 0);
53    /// assert_eq!(UnboundedShr::unbounded_shr(-16i8, 200), -1);
54    /// ```
55    fn unbounded_shr(self, rhs: u32) -> <Self as Shr<u32>>::Output;
56}
57}
58
59macro_rules! unbounded_shift_impl {
60    (unsigned $($t:ty)*) => {$(
61        c0nst::c0nst! {
62        c0nst impl UnboundedShl for $t {
63            #[inline]
64            fn unbounded_shl(self, rhs: u32) -> Self {
65                if rhs < <$t>::BITS { self << rhs } else { 0 }
66            }
67        }
68        }
69        c0nst::c0nst! {
70        c0nst impl UnboundedShr for $t {
71            #[inline]
72            fn unbounded_shr(self, rhs: u32) -> Self {
73                if rhs < <$t>::BITS { self >> rhs } else { 0 }
74            }
75        }
76        }
77    )*};
78    (signed $($t:ty)*) => {$(
79        c0nst::c0nst! {
80        c0nst impl UnboundedShl for $t {
81            #[inline]
82            fn unbounded_shl(self, rhs: u32) -> Self {
83                if rhs < <$t>::BITS { self << rhs } else { 0 }
84            }
85        }
86        }
87        c0nst::c0nst! {
88        c0nst impl UnboundedShr for $t {
89            #[inline]
90            fn unbounded_shr(self, rhs: u32) -> Self {
91                if rhs < <$t>::BITS {
92                    self >> rhs
93                } else {
94                    // shifting by BITS-1 copies the sign bit everywhere
95                    self >> (<$t>::BITS - 1)
96                }
97            }
98        }
99        }
100    )*};
101}
102
103unbounded_shift_impl!(unsigned usize u8 u16 u32 u64 u128);
104unbounded_shift_impl!(signed isize i8 i16 i32 i64 i128);
105
106c0nst::c0nst! {
107/// Performs a funnel left shift on a double-width value formed from two words.
108pub c0nst trait FunnelShl: Sized {
109    /// Funnel shift left: concatenates `self` (high word) with `rhs` (low
110    /// word), shifts the combination left by `n`, and returns the high word
111    /// — i.e. `(self << n) | (rhs >> (BITS - n))`. Like std, this is only
112    /// provided for unsigned types.
113    ///
114    /// # Panics
115    ///
116    /// Panics if `n >= BITS`.
117    ///
118    /// ```
119    /// use const_num_traits::FunnelShl;
120    ///
121    /// assert_eq!(FunnelShl::funnel_shl(0x01u8, 0x80, 1), 0x03);
122    /// ```
123    type Output;
124    fn funnel_shl(self, rhs: Self, n: u32) -> Self::Output;
125}
126}
127
128c0nst::c0nst! {
129/// Performs a funnel right shift on a double-width value formed from two words.
130pub c0nst trait FunnelShr: Sized {
131    /// Funnel shift right: concatenates `self` (high word) with `rhs` (low
132    /// word), shifts the combination right by `n`, and returns the low word
133    /// — i.e. `(rhs >> n) | (self << (BITS - n))`. Like std, this is only
134    /// provided for unsigned types.
135    ///
136    /// # Panics
137    ///
138    /// Panics if `n >= BITS`.
139    ///
140    /// ```
141    /// use const_num_traits::FunnelShr;
142    ///
143    /// assert_eq!(FunnelShr::funnel_shr(0x01u8, 0x80, 1), 0xC0);
144    /// ```
145    type Output;
146    fn funnel_shr(self, rhs: Self, n: u32) -> Self::Output;
147}
148}
149
150macro_rules! funnel_shift_impl {
151    ($($t:ty)*) => {$(
152        c0nst::c0nst! {
153        c0nst impl FunnelShl for $t {
154            type Output = $t;
155            #[inline]
156            #[track_caller]
157            fn funnel_shl(self, rhs: Self, n: u32) -> Self {
158                assert!(n < <$t>::BITS, "attempt to funnel shift left with overflow");
159                if n == 0 {
160                    self
161                } else {
162                    (self << n) | (rhs >> (<$t>::BITS - n))
163                }
164            }
165        }
166        }
167        c0nst::c0nst! {
168        c0nst impl FunnelShr for $t {
169            type Output = $t;
170            #[inline]
171            #[track_caller]
172            fn funnel_shr(self, rhs: Self, n: u32) -> Self {
173                assert!(n < <$t>::BITS, "attempt to funnel shift right with overflow");
174                if n == 0 {
175                    rhs
176                } else {
177                    (rhs >> n) | (self << (<$t>::BITS - n))
178                }
179            }
180        }
181        }
182    )*};
183}
184
185funnel_shift_impl!(usize u8 u16 u32 u64 u128);
186
187c0nst::c0nst! {
188/// Performs a lossless (exactly reversible) left shift.
189pub c0nst trait ShlExact: Sized + [c0nst] Shl<u32> {
190    /// Exact shift left. Computes `self << rhs` if no bits would be shifted
191    /// out (so the operation can be losslessly reversed), `None` otherwise.
192    ///
193    /// ```
194    /// use const_num_traits::ShlExact;
195    ///
196    /// assert_eq!(ShlExact::shl_exact(0x11u8, 3), Some(0x88));
197    /// assert_eq!(ShlExact::shl_exact(0x11u8, 4), None);
198    /// ```
199    fn shl_exact(self, rhs: u32) -> Option<<Self as Shl<u32>>::Output>;
200}
201}
202
203c0nst::c0nst! {
204/// Performs a lossless (exactly reversible) right shift.
205pub c0nst trait ShrExact: Sized + [c0nst] Shr<u32> {
206    /// Exact shift right. Computes `self >> rhs` if no one-bits would be
207    /// shifted out (so the operation can be losslessly reversed), `None`
208    /// otherwise.
209    ///
210    /// ```
211    /// use const_num_traits::ShrExact;
212    ///
213    /// assert_eq!(ShrExact::shr_exact(0x88u8, 3), Some(0x11));
214    /// assert_eq!(ShrExact::shr_exact(0x88u8, 4), None);
215    /// ```
216    fn shr_exact(self, rhs: u32) -> Option<<Self as Shr<u32>>::Output>;
217}
218}
219
220macro_rules! exact_shift_impl {
221    (unsigned $($t:ty)*) => {$(
222        c0nst::c0nst! {
223        c0nst impl ShlExact for $t {
224            #[inline]
225            fn shl_exact(self, rhs: u32) -> Option<Self> {
226                if rhs <= <$t>::leading_zeros(self) && rhs < <$t>::BITS {
227                    Some(self << rhs)
228                } else {
229                    None
230                }
231            }
232        }
233        }
234        exact_shift_impl!(@shr $t);
235    )*};
236    (signed $($t:ty)*) => {$(
237        c0nst::c0nst! {
238        c0nst impl ShlExact for $t {
239            #[inline]
240            fn shl_exact(self, rhs: u32) -> Option<Self> {
241                // for negative values the sign-extension bits are the
242                // recoverable ones, hence leading_ones
243                if rhs < <$t>::leading_zeros(self) || rhs < <$t>::leading_ones(self) {
244                    Some(self << rhs)
245                } else {
246                    None
247                }
248            }
249        }
250        }
251        exact_shift_impl!(@shr $t);
252    )*};
253    (@shr $t:ty) => {
254        c0nst::c0nst! {
255        c0nst impl ShrExact for $t {
256            #[inline]
257            fn shr_exact(self, rhs: u32) -> Option<Self> {
258                if rhs <= <$t>::trailing_zeros(self) && rhs < <$t>::BITS {
259                    Some(self >> rhs)
260                } else {
261                    None
262                }
263            }
264        }
265        }
266    };
267}
268
269exact_shift_impl!(unsigned usize u8 u16 u32 u64 u128);
270exact_shift_impl!(signed isize i8 i16 i32 i64 i128);
271
272c0nst::c0nst! {
273/// Finds the index of the highest one-bit.
274pub c0nst trait HighestOne: Sized {
275    /// Returns the index of the highest bit set to one, or `None` if the
276    /// value is zero.
277    ///
278    /// ```
279    /// use const_num_traits::HighestOne;
280    ///
281    /// assert_eq!(HighestOne::highest_one(0b0101_0000u8), Some(6));
282    /// assert_eq!(HighestOne::highest_one(0u8), None);
283    /// ```
284    fn highest_one(self) -> Option<u32>;
285}
286}
287
288c0nst::c0nst! {
289/// Finds the index of the lowest one-bit.
290pub c0nst trait LowestOne: Sized {
291    /// Returns the index of the lowest bit set to one, or `None` if the
292    /// value is zero.
293    ///
294    /// ```
295    /// use const_num_traits::LowestOne;
296    ///
297    /// assert_eq!(LowestOne::lowest_one(0b0101_0000u8), Some(4));
298    /// assert_eq!(LowestOne::lowest_one(0u8), None);
299    /// ```
300    fn lowest_one(self) -> Option<u32>;
301}
302}
303
304c0nst::c0nst! {
305/// Isolates the highest one-bit, branchlessly.
306pub c0nst trait IsolateHighestOne: Sized {
307    /// Returns `self` with only its highest one-bit kept, or 0 if the value
308    /// is zero.
309    ///
310    /// ```
311    /// use const_num_traits::IsolateHighestOne;
312    ///
313    /// assert_eq!(IsolateHighestOne::isolate_highest_one(0b0101_0000u8), 0b0100_0000);
314    /// ```
315    type Output;
316    fn isolate_highest_one(self) -> Self::Output;
317}
318}
319
320c0nst::c0nst! {
321/// Isolates the lowest one-bit, branchlessly.
322pub c0nst trait IsolateLowestOne: Sized {
323    /// Returns `self` with only its lowest one-bit kept, or 0 if the value
324    /// is zero.
325    ///
326    /// ```
327    /// use const_num_traits::IsolateLowestOne;
328    ///
329    /// assert_eq!(IsolateLowestOne::isolate_lowest_one(0b0101_0000u8), 0b0001_0000);
330    /// ```
331    type Output;
332    fn isolate_lowest_one(self) -> Self::Output;
333}
334}
335
336macro_rules! isolate_one_impl {
337    // operate on the unsigned bit pattern; `$u` is `$t` itself for the
338    // unsigned instantiations
339    ($($t:ty => $u:ty;)*) => {$(
340        c0nst::c0nst! {
341        c0nst impl HighestOne for $t {
342            #[inline]
343            fn highest_one(self) -> Option<u32> {
344                if self == 0 {
345                    None
346                } else {
347                    Some(<$t>::BITS - 1 - <$t>::leading_zeros(self))
348                }
349            }
350        }
351        }
352
353        c0nst::c0nst! {
354        c0nst impl LowestOne for $t {
355            #[inline]
356            fn lowest_one(self) -> Option<u32> {
357                if self == 0 {
358                    None
359                } else {
360                    Some(<$t>::trailing_zeros(self))
361                }
362            }
363        }
364        }
365
366        c0nst::c0nst! {
367        c0nst impl IsolateHighestOne for $t {
368            type Output = $t;
369            #[inline]
370            fn isolate_highest_one(self) -> Self {
371                let bits = self as $u;
372                (bits & ((1 as $u) << (<$u>::BITS - 1)).wrapping_shr(<$u>::leading_zeros(bits))) as $t
373            }
374        }
375        }
376
377        c0nst::c0nst! {
378        c0nst impl IsolateLowestOne for $t {
379            type Output = $t;
380            #[inline]
381            fn isolate_lowest_one(self) -> Self {
382                self & <$t>::wrapping_neg(self)
383            }
384        }
385        }
386    )*};
387}
388
389isolate_one_impl! {
390    u8 => u8; u16 => u16; u32 => u32; u64 => u64; usize => usize; u128 => u128;
391    i8 => u8; i16 => u16; i32 => u32; i64 => u64; isize => usize; i128 => u128;
392}
393
394c0nst::c0nst! {
395/// Computes the minimal number of bits required to represent an unsigned value.
396pub c0nst trait BitWidth: Sized {
397    /// Returns the minimum number of bits required to represent `self`,
398    /// i.e. `BITS - leading_zeros`. Returns 0 for 0. Like std, this is only
399    /// provided for unsigned types.
400    ///
401    /// ```
402    /// use const_num_traits::BitWidth;
403    ///
404    /// assert_eq!(BitWidth::bit_width(0u8), 0);
405    /// assert_eq!(BitWidth::bit_width(0b0101_0000u8), 7);
406    /// ```
407    fn bit_width(self) -> u32;
408}
409}
410
411macro_rules! bit_width_impl {
412    ($($t:ty)*) => {$(
413        c0nst::c0nst! {
414        c0nst impl BitWidth for $t {
415            #[inline]
416            fn bit_width(self) -> u32 {
417                <$t>::BITS - <$t>::leading_zeros(self)
418            }
419        }
420        }
421    )*};
422}
423
424bit_width_impl!(usize u8 u16 u32 u64 u128);
425
426c0nst::c0nst! {
427/// Scatters bits through a mask (the PDEP operation).
428pub c0nst trait DepositBits: Sized {
429    /// Scatters the contiguous low-order bits of `self` into the positions
430    /// of the one-bits of `mask` (the PDEP operation). All other bits of the
431    /// result are zero. Like std, this is only provided for unsigned types.
432    ///
433    /// ```
434    /// use const_num_traits::DepositBits;
435    ///
436    /// assert_eq!(DepositBits::deposit_bits(0b101u8, 0b1111_0000), 0b0101_0000);
437    /// ```
438    type Output;
439    fn deposit_bits(self, mask: Self) -> Self::Output;
440}
441}
442
443c0nst::c0nst! {
444/// Gathers bits through a mask (the PEXT operation).
445pub c0nst trait ExtractBits: Sized {
446    /// Gathers the bits of `self` selected by the one-bits of `mask` into
447    /// the contiguous low-order bits of the result (the PEXT operation).
448    /// Like std, this is only provided for unsigned types.
449    ///
450    /// ```
451    /// use const_num_traits::ExtractBits;
452    ///
453    /// assert_eq!(ExtractBits::extract_bits(0b0101_0011u8, 0b1111_0000), 0b101);
454    /// ```
455    type Output;
456    fn extract_bits(self, mask: Self) -> Self::Output;
457}
458}
459
460macro_rules! deposit_extract_impl {
461    ($($t:ty)*) => {$(
462        c0nst::c0nst! {
463        c0nst impl DepositBits for $t {
464            type Output = $t;
465            #[inline]
466            fn deposit_bits(self, mask: Self) -> Self {
467                let mut result: $t = 0;
468                let mut remaining = mask;
469                let mut bb: $t = 1;
470                while remaining != 0 {
471                    let lowest = remaining & <$t>::wrapping_neg(remaining);
472                    // branchless on the operand: mask is all-ones iff bit `bb` of self is set
473                    let bit_mask = (((self & bb) != 0) as $t).wrapping_neg();
474                    result |= lowest & bit_mask;
475                    remaining &= remaining - 1;
476                    bb = <$t>::wrapping_shl(bb, 1);
477                }
478                result
479            }
480        }
481        }
482
483        c0nst::c0nst! {
484        c0nst impl ExtractBits for $t {
485            type Output = $t;
486            #[inline]
487            fn extract_bits(self, mask: Self) -> Self {
488                let mut result: $t = 0;
489                let mut remaining = mask;
490                let mut bb: $t = 1;
491                while remaining != 0 {
492                    let lowest = remaining & <$t>::wrapping_neg(remaining);
493                    // branchless on the operand: mask is all-ones iff bit `lowest` of self is set
494                    let bit_mask = (((self & lowest) != 0) as $t).wrapping_neg();
495                    result |= bb & bit_mask;
496                    remaining &= remaining - 1;
497                    bb = <$t>::wrapping_shl(bb, 1);
498                }
499                result
500            }
501        }
502        }
503    )*};
504}
505
506deposit_extract_impl!(usize u8 u16 u32 u64 u128);
507
508#[cfg(test)]
509mod tests {
510    use super::*;
511
512    #[test]
513    fn unbounded_shifts() {
514        assert_eq!(UnboundedShl::unbounded_shl(1u8, 7), 0x80);
515        assert_eq!(UnboundedShl::unbounded_shl(1u8, 8), 0);
516        assert_eq!(UnboundedShr::unbounded_shr(0x80u8, 8), 0);
517        assert_eq!(UnboundedShr::unbounded_shr(-1i8, 100), -1);
518        assert_eq!(UnboundedShr::unbounded_shr(i8::MAX, 100), 0);
519    }
520
521    #[test]
522    fn funnel_shifts() {
523        // 0x0180 << 1 = 0x0300 -> high byte 0x03
524        assert_eq!(FunnelShl::funnel_shl(0x01u8, 0x80, 1), 0x03);
525        assert_eq!(FunnelShl::funnel_shl(0xABu8, 0xCD, 0), 0xAB);
526        // 0x0180 >> 1 = 0x00C0 -> low byte 0xC0
527        assert_eq!(FunnelShr::funnel_shr(0x01u8, 0x80, 1), 0xC0);
528        assert_eq!(FunnelShr::funnel_shr(0xABu8, 0xCD, 0), 0xCD);
529        // rotation is a funnel shift with both words equal
530        assert_eq!(
531            FunnelShl::funnel_shl(0x81u8, 0x81, 1),
532            0x81u8.rotate_left(1)
533        );
534    }
535
536    #[test]
537    #[should_panic(expected = "attempt to funnel shift left with overflow")]
538    fn funnel_shl_panics() {
539        let _ = FunnelShl::funnel_shl(1u8, 1, 8);
540    }
541
542    #[test]
543    fn exact_shifts() {
544        assert_eq!(ShlExact::shl_exact(0x11u8, 3), Some(0x88));
545        assert_eq!(ShlExact::shl_exact(0x11u8, 4), None);
546        assert_eq!(ShrExact::shr_exact(0x88u8, 3), Some(0x11));
547        assert_eq!(ShrExact::shr_exact(0x88u8, 4), None);
548        assert_eq!(ShrExact::shr_exact(0u8, 7), Some(0));
549        assert_eq!(ShrExact::shr_exact(0u8, 8), None);
550        // negative values: sign bits are recoverable
551        assert_eq!(ShlExact::shl_exact(-1i8, 7), Some(i8::MIN));
552        assert_eq!(ShlExact::shl_exact(-2i8, 6), Some(i8::MIN));
553        assert_eq!(ShlExact::shl_exact(-2i8, 7), None);
554        assert_eq!(ShlExact::shl_exact(1i8, 6), Some(64));
555        assert_eq!(ShlExact::shl_exact(1i8, 7), None);
556    }
557
558    #[test]
559    fn isolate() {
560        assert_eq!(HighestOne::highest_one(0b0101_0000u8), Some(6));
561        assert_eq!(HighestOne::highest_one(0u8), None);
562        assert_eq!(LowestOne::lowest_one(0b0101_0000u8), Some(4));
563        assert_eq!(LowestOne::lowest_one(0i64), None);
564        assert_eq!(
565            IsolateHighestOne::isolate_highest_one(0b0101_0000u8),
566            0b0100_0000
567        );
568        assert_eq!(
569            IsolateLowestOne::isolate_lowest_one(0b0101_0000u8),
570            0b0001_0000
571        );
572        assert_eq!(IsolateHighestOne::isolate_highest_one(0u8), 0);
573        assert_eq!(IsolateLowestOne::isolate_lowest_one(0u8), 0);
574        // signed: operates on the bit pattern
575        assert_eq!(HighestOne::highest_one(-1i8), Some(7));
576        assert_eq!(IsolateHighestOne::isolate_highest_one(-1i8), i8::MIN);
577        assert_eq!(IsolateLowestOne::isolate_lowest_one(-2i8), 2);
578    }
579
580    #[test]
581    fn bit_width() {
582        assert_eq!(BitWidth::bit_width(0u8), 0);
583        assert_eq!(BitWidth::bit_width(1u8), 1);
584        assert_eq!(BitWidth::bit_width(255u8), 8);
585        assert_eq!(BitWidth::bit_width(0x0101u16), 9);
586    }
587
588    #[test]
589    fn deposit_extract() {
590        assert_eq!(DepositBits::deposit_bits(0b101u8, 0b1111_0000), 0b0101_0000);
591        assert_eq!(DepositBits::deposit_bits(0xFFu8, 0b1010_1010), 0b1010_1010);
592        assert_eq!(ExtractBits::extract_bits(0b0101_0011u8, 0b1111_0000), 0b101);
593        assert_eq!(ExtractBits::extract_bits(0xFFu8, 0b1010_1010), 0b1111);
594        // extract is the inverse of deposit on the masked bits
595        let mask = 0b0110_1001u8;
596        for x in 0u8..16 {
597            let deposited = DepositBits::deposit_bits(x, mask);
598            assert_eq!(deposited & !mask, 0);
599            assert_eq!(ExtractBits::extract_bits(deposited, mask), x);
600        }
601    }
602}