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}