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/// Scatters bits through a mask (the PDEP operation).
441pub c0nst trait DepositBits: Sized {
442    /// Scatters the contiguous low-order bits of `self` into the positions
443    /// of the one-bits of `mask` (the PDEP operation). All other bits of the
444    /// result are zero. Like std, this is only provided for unsigned types.
445    ///
446    /// ```
447    /// use const_num_traits::DepositBits;
448    ///
449    /// assert_eq!(DepositBits::deposit_bits(0b101u8, 0b1111_0000), 0b0101_0000);
450    /// ```
451    type Output;
452    fn deposit_bits(self, mask: Self) -> Self::Output;
453}
454}
455
456c0nst::c0nst! {
457/// Gathers bits through a mask (the PEXT operation).
458pub c0nst trait ExtractBits: Sized {
459    /// Gathers the bits of `self` selected by the one-bits of `mask` into
460    /// the contiguous low-order bits of the result (the PEXT operation).
461    /// Like std, this is only provided for unsigned types.
462    ///
463    /// ```
464    /// use const_num_traits::ExtractBits;
465    ///
466    /// assert_eq!(ExtractBits::extract_bits(0b0101_0011u8, 0b1111_0000), 0b101);
467    /// ```
468    type Output;
469    fn extract_bits(self, mask: Self) -> Self::Output;
470}
471}
472
473macro_rules! deposit_extract_impl {
474    ($($t:ty)*) => {$(
475        c0nst::c0nst! {
476        c0nst impl DepositBits for $t {
477            type Output = $t;
478            #[inline]
479            fn deposit_bits(self, mask: Self) -> Self {
480                let mut result: $t = 0;
481                let mut remaining = mask;
482                let mut bb: $t = 1;
483                while remaining != 0 {
484                    let lowest = remaining & <$t>::wrapping_neg(remaining);
485                    // branchless on the operand: mask is all-ones iff bit `bb` of self is set
486                    let bit_mask = (((self & bb) != 0) as $t).wrapping_neg();
487                    result |= lowest & bit_mask;
488                    remaining &= remaining - 1;
489                    bb = <$t>::wrapping_shl(bb, 1);
490                }
491                result
492            }
493        }
494        }
495
496        c0nst::c0nst! {
497        c0nst impl ExtractBits for $t {
498            type Output = $t;
499            #[inline]
500            fn extract_bits(self, mask: Self) -> Self {
501                let mut result: $t = 0;
502                let mut remaining = mask;
503                let mut bb: $t = 1;
504                while remaining != 0 {
505                    let lowest = remaining & <$t>::wrapping_neg(remaining);
506                    // branchless on the operand: mask is all-ones iff bit `lowest` of self is set
507                    let bit_mask = (((self & lowest) != 0) as $t).wrapping_neg();
508                    result |= bb & bit_mask;
509                    remaining &= remaining - 1;
510                    bb = <$t>::wrapping_shl(bb, 1);
511                }
512                result
513            }
514        }
515        }
516    )*};
517}
518
519deposit_extract_impl!(usize u8 u16 u32 u64 u128);
520
521#[cfg(test)]
522mod tests {
523    use super::*;
524
525    #[test]
526    fn unbounded_shifts() {
527        assert_eq!(UnboundedShl::unbounded_shl(1u8, 7), 0x80);
528        assert_eq!(UnboundedShl::unbounded_shl(1u8, 8), 0);
529        assert_eq!(UnboundedShr::unbounded_shr(0x80u8, 8), 0);
530        assert_eq!(UnboundedShr::unbounded_shr(-1i8, 100), -1);
531        assert_eq!(UnboundedShr::unbounded_shr(i8::MAX, 100), 0);
532    }
533
534    #[test]
535    fn funnel_shifts() {
536        // 0x0180 << 1 = 0x0300 -> high byte 0x03
537        assert_eq!(FunnelShl::funnel_shl(0x01u8, 0x80, 1), 0x03);
538        assert_eq!(FunnelShl::funnel_shl(0xABu8, 0xCD, 0), 0xAB);
539        // 0x0180 >> 1 = 0x00C0 -> low byte 0xC0
540        assert_eq!(FunnelShr::funnel_shr(0x01u8, 0x80, 1), 0xC0);
541        assert_eq!(FunnelShr::funnel_shr(0xABu8, 0xCD, 0), 0xCD);
542        // rotation is a funnel shift with both words equal
543        assert_eq!(
544            FunnelShl::funnel_shl(0x81u8, 0x81, 1),
545            0x81u8.rotate_left(1)
546        );
547    }
548
549    #[test]
550    #[should_panic(expected = "attempt to funnel shift left with overflow")]
551    fn funnel_shl_panics() {
552        let _ = FunnelShl::funnel_shl(1u8, 1, 8);
553    }
554
555    #[test]
556    fn exact_shifts() {
557        assert_eq!(ShlExact::shl_exact(0x11u8, 3), Some(0x88));
558        assert_eq!(ShlExact::shl_exact(0x11u8, 4), None);
559        assert_eq!(ShrExact::shr_exact(0x88u8, 3), Some(0x11));
560        assert_eq!(ShrExact::shr_exact(0x88u8, 4), None);
561        assert_eq!(ShrExact::shr_exact(0u8, 7), Some(0));
562        assert_eq!(ShrExact::shr_exact(0u8, 8), None);
563        // negative values: sign bits are recoverable
564        assert_eq!(ShlExact::shl_exact(-1i8, 7), Some(i8::MIN));
565        assert_eq!(ShlExact::shl_exact(-2i8, 6), Some(i8::MIN));
566        assert_eq!(ShlExact::shl_exact(-2i8, 7), None);
567        assert_eq!(ShlExact::shl_exact(1i8, 6), Some(64));
568        assert_eq!(ShlExact::shl_exact(1i8, 7), None);
569    }
570
571    #[test]
572    fn isolate() {
573        assert_eq!(HighestOne::highest_one(0b0101_0000u8), Some(6));
574        assert_eq!(HighestOne::highest_one(0u8), None);
575        assert_eq!(LowestOne::lowest_one(0b0101_0000u8), Some(4));
576        assert_eq!(LowestOne::lowest_one(0i64), None);
577        assert_eq!(
578            IsolateHighestOne::isolate_highest_one(0b0101_0000u8),
579            0b0100_0000
580        );
581        assert_eq!(
582            IsolateLowestOne::isolate_lowest_one(0b0101_0000u8),
583            0b0001_0000
584        );
585        assert_eq!(IsolateHighestOne::isolate_highest_one(0u8), 0);
586        assert_eq!(IsolateLowestOne::isolate_lowest_one(0u8), 0);
587        // signed: operates on the bit pattern
588        assert_eq!(HighestOne::highest_one(-1i8), Some(7));
589        assert_eq!(IsolateHighestOne::isolate_highest_one(-1i8), i8::MIN);
590        assert_eq!(IsolateLowestOne::isolate_lowest_one(-2i8), 2);
591    }
592
593    #[test]
594    fn bit_width() {
595        assert_eq!(BitWidth::bit_width(0u8), 0);
596        assert_eq!(BitWidth::bit_width(1u8), 1);
597        assert_eq!(BitWidth::bit_width(255u8), 8);
598        assert_eq!(BitWidth::bit_width(0x0101u16), 9);
599    }
600
601    #[test]
602    fn deposit_extract() {
603        assert_eq!(DepositBits::deposit_bits(0b101u8, 0b1111_0000), 0b0101_0000);
604        assert_eq!(DepositBits::deposit_bits(0xFFu8, 0b1010_1010), 0b1010_1010);
605        assert_eq!(ExtractBits::extract_bits(0b0101_0011u8, 0b1111_0000), 0b101);
606        assert_eq!(ExtractBits::extract_bits(0xFFu8, 0b1010_1010), 0b1111);
607        // extract is the inverse of deposit on the masked bits
608        let mask = 0b0110_1001u8;
609        for x in 0u8..16 {
610            let deposited = DepositBits::deposit_bits(x, mask);
611            assert_eq!(deposited & !mask, 0);
612            assert_eq!(ExtractBits::extract_bits(deposited, mask), x);
613        }
614    }
615}