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}