Skip to main content

fixed_bigint/fixeduint/
bit_ops_impl.rs

1use super::{FixedUInt, MachineWord, const_shl_ct, const_shl_impl, const_shr_ct, const_shr_impl};
2
3use crate::machineword::ConstMachineWord;
4use const_num_traits::{
5    CheckedShl, CheckedShr, ConstZero, OverflowingShl, OverflowingShr, UnboundedShl, UnboundedShr,
6    WrappingShl, WrappingShr,
7};
8use const_num_traits::{Nct, Personality, PersonalityTag};
9
10c0nst::c0nst! {
11    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Not for FixedUInt<T, N, P> {
12        type Output = Self;
13        fn not(self) -> Self::Output {
14            let mut ret = <Self as ConstZero>::ZERO;
15            let mut i = 0;
16            while i < N {
17                ret.array[i] = !self.array[i];
18                i += 1;
19            }
20            ret
21        }
22    }
23
24    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitAnd<&FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
25        type Output = FixedUInt<T, N, P>;
26        fn bitand(self, other: &FixedUInt<T, N, P>) -> Self::Output {
27            let mut ret = <FixedUInt<T, N, P> as ConstZero>::ZERO;
28            let mut i = 0;
29            while i < N {
30                ret.array[i] = self.array[i] & other.array[i];
31                i += 1;
32            }
33            ret
34        }
35    }
36
37    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitAnd for FixedUInt<T, N, P> {
38        type Output = Self;
39        fn bitand(self, other: Self) -> Self::Output {
40            (&self).bitand(&other)
41        }
42    }
43
44    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitAnd<&FixedUInt<T, N, P>> for FixedUInt<T, N, P> {
45        type Output = Self;
46        fn bitand(self, other: &FixedUInt<T, N, P>) -> Self::Output {
47            (&self).bitand(other)
48        }
49    }
50
51    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitAnd<FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
52        type Output = FixedUInt<T, N, P>;
53        fn bitand(self, other: FixedUInt<T, N, P>) -> Self::Output {
54            self.bitand(&other)
55        }
56    }
57
58    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitAndAssign for FixedUInt<T, N, P> {
59        fn bitand_assign(&mut self, other: Self) {
60            let mut i = 0;
61            while i < N {
62                self.array[i] &= other.array[i];
63                i += 1;
64            }
65        }
66    }
67
68    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitOr<&FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
69        type Output = FixedUInt<T, N, P>;
70        fn bitor(self, other: &FixedUInt<T, N, P>) -> Self::Output {
71            let mut ret = <FixedUInt<T, N, P> as ConstZero>::ZERO;
72            let mut i = 0;
73            while i < N {
74                ret.array[i] = self.array[i] | other.array[i];
75                i += 1;
76            }
77            ret
78        }
79    }
80
81    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitOr for FixedUInt<T, N, P> {
82        type Output = Self;
83        fn bitor(self, other: Self) -> Self::Output {
84            (&self).bitor(&other)
85        }
86    }
87
88    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitOr<&FixedUInt<T, N, P>> for FixedUInt<T, N, P> {
89        type Output = Self;
90        fn bitor(self, other: &FixedUInt<T, N, P>) -> Self::Output {
91            (&self).bitor(other)
92        }
93    }
94
95    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitOr<FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
96        type Output = FixedUInt<T, N, P>;
97        fn bitor(self, other: FixedUInt<T, N, P>) -> Self::Output {
98            self.bitor(&other)
99        }
100    }
101
102    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitOrAssign for FixedUInt<T, N, P> {
103        fn bitor_assign(&mut self, other: Self) {
104            let mut i = 0;
105            while i < N {
106                self.array[i] |= other.array[i];
107                i += 1;
108            }
109        }
110    }
111
112    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitXor<&FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
113        type Output = FixedUInt<T, N, P>;
114        fn bitxor(self, other: &FixedUInt<T, N, P>) -> Self::Output {
115            let mut ret = <FixedUInt<T, N, P> as ConstZero>::ZERO;
116            let mut i = 0;
117            while i < N {
118                ret.array[i] = self.array[i] ^ other.array[i];
119                i += 1;
120            }
121            ret
122        }
123    }
124
125    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitXor for FixedUInt<T, N, P> {
126        type Output = Self;
127        fn bitxor(self, other: Self) -> Self::Output {
128            (&self).bitxor(&other)
129        }
130    }
131
132    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitXor<&FixedUInt<T, N, P>> for FixedUInt<T, N, P> {
133        type Output = Self;
134        fn bitxor(self, other: &FixedUInt<T, N, P>) -> Self::Output {
135            (&self).bitxor(other)
136        }
137    }
138
139    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitXor<FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
140        type Output = FixedUInt<T, N, P>;
141        fn bitxor(self, other: FixedUInt<T, N, P>) -> Self::Output {
142            self.bitxor(&other)
143        }
144    }
145
146    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitXorAssign for FixedUInt<T, N, P> {
147        fn bitxor_assign(&mut self, other: Self) {
148            let mut i = 0;
149            while i < N {
150                self.array[i] ^= other.array[i];
151                i += 1;
152            }
153        }
154    }
155
156    // Primary Shl/Shr implementations
157    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<usize> for FixedUInt<T, N, P> {
158        type Output = Self;
159        fn shl(self, bits: usize) -> Self::Output {
160            let mut result = self;
161            match P::TAG {
162                PersonalityTag::Nct => const_shl_impl(&mut result, bits),
163                PersonalityTag::Ct => const_shl_ct(&mut result, bits),
164            }
165            result
166        }
167    }
168
169    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<usize> for FixedUInt<T, N, P> {
170        type Output = Self;
171        fn shr(self, bits: usize) -> Self::Output {
172            let mut result = self;
173            match P::TAG {
174                PersonalityTag::Nct => const_shr_impl(&mut result, bits),
175                PersonalityTag::Ct => const_shr_ct(&mut result, bits),
176            }
177            result
178        }
179    }
180
181    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<u32> for FixedUInt<T, N, P> {
182        type Output = Self;
183        fn shl(self, bits: u32) -> Self::Output {
184            const_unbounded_shl_u32(self, bits)
185        }
186    }
187
188    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<u32> for FixedUInt<T, N, P> {
189        type Output = Self;
190        fn shr(self, bits: u32) -> Self::Output {
191            const_unbounded_shr_u32(self, bits)
192        }
193    }
194
195    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<&usize> for FixedUInt<T, N, P> {
196        type Output = Self;
197        fn shl(self, bits: &usize) -> Self::Output {
198            self.shl(*bits)
199        }
200    }
201
202    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<&usize> for FixedUInt<T, N, P> {
203        type Output = Self;
204        fn shr(self, bits: &usize) -> Self::Output {
205            self.shr(*bits)
206        }
207    }
208
209    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<&u32> for FixedUInt<T, N, P> {
210        type Output = Self;
211        fn shl(self, bits: &u32) -> Self::Output {
212            self.shl(*bits)
213        }
214    }
215
216    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<&u32> for FixedUInt<T, N, P> {
217        type Output = Self;
218        fn shr(self, bits: &u32) -> Self::Output {
219            self.shr(*bits)
220        }
221    }
222
223    // Shl/Shr for &FixedUInt
224    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<usize> for &FixedUInt<T, N, P> {
225        type Output = FixedUInt<T, N, P>;
226        fn shl(self, bits: usize) -> Self::Output {
227            FixedUInt::from_array(self.array).shl(bits)
228        }
229    }
230
231    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<usize> for &FixedUInt<T, N, P> {
232        type Output = FixedUInt<T, N, P>;
233        fn shr(self, bits: usize) -> Self::Output {
234            FixedUInt::from_array(self.array).shr(bits)
235        }
236    }
237
238    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<u32> for &FixedUInt<T, N, P> {
239        type Output = FixedUInt<T, N, P>;
240        fn shl(self, bits: u32) -> Self::Output {
241            FixedUInt::from_array(self.array).shl(bits)
242        }
243    }
244
245    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<u32> for &FixedUInt<T, N, P> {
246        type Output = FixedUInt<T, N, P>;
247        fn shr(self, bits: u32) -> Self::Output {
248            FixedUInt::from_array(self.array).shr(bits)
249        }
250    }
251
252    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<&usize> for &FixedUInt<T, N, P> {
253        type Output = FixedUInt<T, N, P>;
254        fn shl(self, bits: &usize) -> Self::Output {
255            FixedUInt::from_array(self.array).shl(*bits)
256        }
257    }
258
259    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<&usize> for &FixedUInt<T, N, P> {
260        type Output = FixedUInt<T, N, P>;
261        fn shr(self, bits: &usize) -> Self::Output {
262            FixedUInt::from_array(self.array).shr(*bits)
263        }
264    }
265
266    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<&u32> for &FixedUInt<T, N, P> {
267        type Output = FixedUInt<T, N, P>;
268        fn shl(self, bits: &u32) -> Self::Output {
269            FixedUInt::from_array(self.array).shl(*bits)
270        }
271    }
272
273    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<&u32> for &FixedUInt<T, N, P> {
274        type Output = FixedUInt<T, N, P>;
275        fn shr(self, bits: &u32) -> Self::Output {
276            FixedUInt::from_array(self.array).shr(*bits)
277        }
278    }
279
280    // ShlAssign/ShrAssign
281    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::ShlAssign<usize> for FixedUInt<T, N, P> {
282        fn shl_assign(&mut self, bits: usize) {
283            match P::TAG {
284                PersonalityTag::Nct => const_shl_impl(self, bits),
285                PersonalityTag::Ct => const_shl_ct(self, bits),
286            }
287        }
288    }
289
290    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::ShrAssign<usize> for FixedUInt<T, N, P> {
291        fn shr_assign(&mut self, bits: usize) {
292            match P::TAG {
293                PersonalityTag::Nct => const_shr_impl(self, bits),
294                PersonalityTag::Ct => const_shr_ct(self, bits),
295            }
296        }
297    }
298
299    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::ShlAssign<&usize> for FixedUInt<T, N, P> {
300        fn shl_assign(&mut self, bits: &usize) {
301            match P::TAG {
302                PersonalityTag::Nct => const_shl_impl(self, *bits),
303                PersonalityTag::Ct => const_shl_ct(self, *bits),
304            }
305        }
306    }
307
308    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::ShrAssign<&usize> for FixedUInt<T, N, P> {
309        fn shr_assign(&mut self, bits: &usize) {
310            match P::TAG {
311                PersonalityTag::Nct => const_shr_impl(self, *bits),
312                PersonalityTag::Ct => const_shr_ct(self, *bits),
313            }
314        }
315    }
316
317    // Shared body for `Shl<u32>` and `UnboundedShl::unbounded_shl`:
318    // shift by a u32 amount, with values outside [0, BIT_SIZE) collapsing
319    // to zero. Centralizing keeps the two entry points in sync.
320    pub(crate) c0nst fn const_unbounded_shl_u32<
321        T: [c0nst] ConstMachineWord + MachineWord,
322        const N: usize,
323        P: Personality,
324    >(
325        target: FixedUInt<T, N, P>,
326        bits: u32,
327    ) -> FixedUInt<T, N, P> {
328        match P::TAG {
329            PersonalityTag::Nct => {
330                let (shift, overflow) =
331                    normalize_shift_amount(bits, FixedUInt::<T, N, P>::BIT_SIZE);
332                if overflow {
333                    <FixedUInt<T, N, P> as ConstZero>::ZERO
334                } else {
335                    target << shift
336                }
337            }
338            PersonalityTag::Ct => {
339                // Skip `normalize_shift_amount` entirely. Its `if bits >=
340                // bit_size_u32` is a tainted-flag branch and `bits %
341                // bit_size_u32` is a variable-time modulo when bits is a
342                // secret — both leaks even though current LLVM may pattern-
343                // match them on power-of-2 BIT_SIZE. `const_shl_ct`'s
344                // barrel shifter already collapses out-of-range shifts to
345                // zero (via `const_shl_impl`'s `nwords >= N` zero-out), so
346                // the overflow detection is redundant for the Ct path —
347                // EXCEPT for the cast to usize on 16-bit-usize targets,
348                // where `bits as usize` truncates and could undo the
349                // saturation. Cap branchlessly to `BIT_SIZE` first; for
350                // every priority diagonal BIT_SIZE fits in u16, so the
351                // capped value casts losslessly even on AVR.
352                let bit_size_u32 = FixedUInt::<T, N, P>::BIT_SIZE as u32;
353                let capped = const_ct_min_u32(bits, bit_size_u32);
354                target << (capped as usize)
355            }
356        }
357    }
358
359    /// Mirror of [`const_unbounded_shl_u32`] for right-shifts.
360    pub(crate) c0nst fn const_unbounded_shr_u32<
361        T: [c0nst] ConstMachineWord + MachineWord,
362        const N: usize,
363        P: Personality,
364    >(
365        target: FixedUInt<T, N, P>,
366        bits: u32,
367    ) -> FixedUInt<T, N, P> {
368        match P::TAG {
369            PersonalityTag::Nct => {
370                let (shift, overflow) =
371                    normalize_shift_amount(bits, FixedUInt::<T, N, P>::BIT_SIZE);
372                if overflow {
373                    <FixedUInt<T, N, P> as ConstZero>::ZERO
374                } else {
375                    target >> shift
376                }
377            }
378            PersonalityTag::Ct => {
379                // See `const_unbounded_shl_u32` for why this skips
380                // `normalize_shift_amount` and caps before casting.
381                let bit_size_u32 = FixedUInt::<T, N, P>::BIT_SIZE as u32;
382                let capped = const_ct_min_u32(bits, bit_size_u32);
383                target >> (capped as usize)
384            }
385        }
386    }
387
388    /// Branchless CT-safe `min(bits, cap)` for u32, used to clamp Ct
389    /// shift amounts to `BIT_SIZE` before casting to usize. The
390    /// `black_box` on the mask is required — without it, LLVM
391    /// recognises the XOR-AND-XOR select idiom and rewrites it into a
392    /// `cmov` whose flag depends on the secret `bits`. Same defence as
393    /// `const_ct_select` (PR #118).
394    c0nst fn const_ct_min_u32(bits: u32, cap: u32) -> u32 {
395        // diff = cap - bits, wraps to negative (high bit set) iff bits > cap.
396        let diff = cap.wrapping_sub(bits);
397        let too_big_bit = (diff >> 31) & 1;
398        let too_big_mask = core::hint::black_box(too_big_bit.wrapping_neg());
399        // bits if !too_big, cap otherwise. XOR-AND-XOR select with
400        // opaque mask.
401        bits ^ (too_big_mask & (bits ^ cap))
402    }
403
404    // Helper to normalize shift amount and detect overflow.
405    // Handles both 16-bit (usize < u32) and 64-bit (bit_size > u32::MAX) platforms.
406    c0nst fn normalize_shift_amount(bits: u32, bit_size: usize) -> (usize, bool) {
407        let bit_size_u32 = bit_size as u32;
408        if bit_size == 0 {
409            // Zero-size type: always overflow
410            (0, true)
411        } else if bit_size_u32 == 0 {
412            // bit_size is a non-zero multiple of 2^32 (huge type on 64-bit).
413            // Since bits is u32, it's always smaller than bit_size. No overflow.
414            (bits as usize, false)
415        } else if bits >= bit_size_u32 {
416            // Normal case: shift exceeds bit width
417            ((bits % bit_size_u32) as usize, true)
418        } else {
419            (bits as usize, false)
420        }
421    }
422
423    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> OverflowingShl for FixedUInt<T, N, P> {
424        type Output = FixedUInt<T, N, P>;
425        fn overflowing_shl(self, bits: u32) -> (Self, bool) {
426            let (shift, overflow) = normalize_shift_amount(bits, Self::BIT_SIZE);
427            let res = core::ops::Shl::<usize>::shl(self, shift);
428            (res, overflow)
429        }
430    }
431
432    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> OverflowingShr for FixedUInt<T, N, P> {
433        type Output = FixedUInt<T, N, P>;
434        fn overflowing_shr(self, bits: u32) -> (Self, bool) {
435            let (shift, overflow) = normalize_shift_amount(bits, Self::BIT_SIZE);
436            let res = core::ops::Shr::<usize>::shr(self, shift);
437            (res, overflow)
438        }
439    }
440
441    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> WrappingShl for FixedUInt<T, N, P> {
442        type Output = FixedUInt<T, N, P>;
443        fn wrapping_shl(self, bits: u32) -> Self {
444            OverflowingShl::overflowing_shl(self, bits).0
445        }
446    }
447
448    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> WrappingShr for FixedUInt<T, N, P> {
449        type Output = FixedUInt<T, N, P>;
450        fn wrapping_shr(self, bits: u32) -> Self {
451            OverflowingShr::overflowing_shr(self, bits).0
452        }
453    }
454
455    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> CheckedShl for FixedUInt<T, N, P> {
456        type Output = FixedUInt<T, N, P>;
457        fn checked_shl(self, bits: u32) -> Option<Self> {
458            let (res, overflow) = OverflowingShl::overflowing_shl(self, bits);
459            if overflow { None } else { Some(res) }
460        }
461    }
462
463    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> CheckedShr for FixedUInt<T, N, P> {
464        type Output = FixedUInt<T, N, P>;
465        fn checked_shr(self, bits: u32) -> Option<Self> {
466            let (res, overflow) = OverflowingShr::overflowing_shr(self, bits);
467            if overflow { None } else { Some(res) }
468        }
469    }
470
471    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> UnboundedShl for FixedUInt<T, N, P> {
472        type Output = FixedUInt<T, N, P>;
473        fn unbounded_shl(self, rhs: u32) -> Self {
474            const_unbounded_shl_u32(self, rhs)
475        }
476    }
477
478    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> UnboundedShr for FixedUInt<T, N, P> {
479        type Output = FixedUInt<T, N, P>;
480        fn unbounded_shr(self, rhs: u32) -> Self {
481            const_unbounded_shr_u32(self, rhs)
482        }
483    }
484
485    // --- Reference-receiver shift impls (see add_sub_impl.rs for rationale) ---
486    //
487    // Output comes from the operator supertrait (`Shl<u32>` / `Shr<u32>`
488    // for `&FixedUInt`, defined earlier in this c0nst! block), so
489    // Output resolves to `FixedUInt<T,N,P>`.
490
491    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> OverflowingShl for &FixedUInt<T, N, P> {
492        type Output = FixedUInt<T, N, P>;
493        fn overflowing_shl(self, bits: u32) -> (FixedUInt<T, N, P>, bool) {
494            <FixedUInt<T, N, P> as OverflowingShl>::overflowing_shl(FixedUInt::from_array(self.array), bits)
495        }
496    }
497
498    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> OverflowingShr for &FixedUInt<T, N, P> {
499        type Output = FixedUInt<T, N, P>;
500        fn overflowing_shr(self, bits: u32) -> (FixedUInt<T, N, P>, bool) {
501            <FixedUInt<T, N, P> as OverflowingShr>::overflowing_shr(FixedUInt::from_array(self.array), bits)
502        }
503    }
504
505    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> WrappingShl for &FixedUInt<T, N, P> {
506        type Output = FixedUInt<T, N, P>;
507        fn wrapping_shl(self, bits: u32) -> FixedUInt<T, N, P> {
508            <FixedUInt<T, N, P> as WrappingShl>::wrapping_shl(FixedUInt::from_array(self.array), bits)
509        }
510    }
511
512    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> WrappingShr for &FixedUInt<T, N, P> {
513        type Output = FixedUInt<T, N, P>;
514        fn wrapping_shr(self, bits: u32) -> FixedUInt<T, N, P> {
515            <FixedUInt<T, N, P> as WrappingShr>::wrapping_shr(FixedUInt::from_array(self.array), bits)
516        }
517    }
518
519    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> CheckedShl for &FixedUInt<T, N, P> {
520        type Output = FixedUInt<T, N, P>;
521        fn checked_shl(self, bits: u32) -> Option<FixedUInt<T, N, P>> {
522            <FixedUInt<T, N, P> as CheckedShl>::checked_shl(FixedUInt::from_array(self.array), bits)
523        }
524    }
525
526    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> CheckedShr for &FixedUInt<T, N, P> {
527        type Output = FixedUInt<T, N, P>;
528        fn checked_shr(self, bits: u32) -> Option<FixedUInt<T, N, P>> {
529            <FixedUInt<T, N, P> as CheckedShr>::checked_shr(FixedUInt::from_array(self.array), bits)
530        }
531    }
532
533    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> UnboundedShl for &FixedUInt<T, N, P> {
534        type Output = FixedUInt<T, N, P>;
535        fn unbounded_shl(self, rhs: u32) -> FixedUInt<T, N, P> {
536            <FixedUInt<T, N, P> as UnboundedShl>::unbounded_shl(FixedUInt::from_array(self.array), rhs)
537        }
538    }
539
540    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> UnboundedShr for &FixedUInt<T, N, P> {
541        type Output = FixedUInt<T, N, P>;
542        fn unbounded_shr(self, rhs: u32) -> FixedUInt<T, N, P> {
543            <FixedUInt<T, N, P> as UnboundedShr>::unbounded_shr(FixedUInt::from_array(self.array), rhs)
544        }
545    }
546
547    // --- HighestOne / LowestOne ---------------------------------------------
548    //
549    // Indices of the highest / lowest set bit. Both reduce to the leading-
550    // and trailing-zero counts we already compute (Nct fast-path through
551    // `const_leading_zeros` / `const_trailing_zeros`; Ct path through the
552    // mask-select `_ct` variants).
553    //
554    // NOTE: NOT constant-time on a `FixedUInt<_, _, Ct>` carrier — the
555    // `Option` return shape leaks whether `self == 0` regardless of what
556    // the caller does with the value. Ct callers whose input might be
557    // zero should mask it separately (`CtIsZero::ct_is_zero`) before
558    // consulting these methods, or avoid them entirely.
559
560    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::HighestOne for FixedUInt<T, N, P> {
561        fn highest_one(self) -> Option<u32> {
562            let lz = <Self as const_num_traits::PrimBits>::leading_zeros(self);
563            if lz as usize == Self::BIT_SIZE {
564                None
565            } else {
566                Some(Self::BIT_SIZE as u32 - 1 - lz)
567            }
568        }
569    }
570
571    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::LowestOne for FixedUInt<T, N, P> {
572        fn lowest_one(self) -> Option<u32> {
573            let tz = <Self as const_num_traits::PrimBits>::trailing_zeros(self);
574            if tz as usize == Self::BIT_SIZE {
575                None
576            } else {
577                Some(tz)
578            }
579        }
580    }
581
582    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::HighestOne for &FixedUInt<T, N, P> {
583        fn highest_one(self) -> Option<u32> {
584            <FixedUInt<T, N, P> as const_num_traits::HighestOne>::highest_one(FixedUInt::from_array(self.array))
585        }
586    }
587
588    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::LowestOne for &FixedUInt<T, N, P> {
589        fn lowest_one(self) -> Option<u32> {
590            <FixedUInt<T, N, P> as const_num_traits::LowestOne>::lowest_one(FixedUInt::from_array(self.array))
591        }
592    }
593
594    // --- BitWidth ----------------------------------------------------------
595    //
596    // Minimum bits to represent self: `BIT_SIZE - leading_zeros(self)`.
597    // Returns 0 for 0. Mirrors std's `u32::BITS - n.leading_zeros()`.
598
599    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::BitWidth for FixedUInt<T, N, P> {
600        fn bit_width(self) -> u32 {
601            Self::BIT_SIZE as u32 - <Self as const_num_traits::PrimBits>::leading_zeros(self)
602        }
603    }
604
605    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::BitWidth for &FixedUInt<T, N, P> {
606        fn bit_width(self) -> u32 {
607            <FixedUInt<T, N, P> as const_num_traits::BitWidth>::bit_width(FixedUInt::from_array(self.array))
608        }
609    }
610
611    // --- BitsPrecision -----------------------------------------------------
612    //
613    // Operating width: for a fixed carrier this is `BIT_SIZE` (= N·word_bits),
614    // value-independent — width == capacity for FixedUInt. Contrast BitWidth
615    // (bit-length, per-value).
616
617    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::BitsPrecision for FixedUInt<T, N, P> {
618        fn bits_precision(&self) -> u32 {
619            Self::BIT_SIZE as u32
620        }
621    }
622
623    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::BitsPrecision for &FixedUInt<T, N, P> {
624        fn bits_precision(&self) -> u32 {
625            FixedUInt::<T, N, P>::BIT_SIZE as u32
626        }
627    }
628
629    // --- WithPrecision -----------------------------------------------------
630    //
631    // The operating width is the type (N words), so widening is the identity —
632    // same as the primitive impls.
633
634    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::WithPrecision for FixedUInt<T, N, P> {
635        fn widen_to_precision(self, _bits_precision: u32) -> Self {
636            self
637        }
638    }
639
640    // --- IsolateHighestOne / IsolateLowestOne ------------------------------
641    //
642    // Mask the value down to just its highest / lowest set bit.
643    // IsolateHighestOne: `0` → `0`, else `1 << (BIT_SIZE - 1 - leading_zeros)`.
644    // IsolateLowestOne: the classic `self & self.wrapping_neg()` trick
645    //   (which yields 0 for 0 input automatically) — and uses arithmetic
646    //   already implemented uniformly across personalities.
647
648    // NOTE: `isolate_highest_one` is NOT constant-time for FixedUInt under
649    // Ct. The `if lz as usize == Self::BIT_SIZE` branch is value-dependent
650    // (it leaks whether `self == 0`), and the `pos`-parameterized shift's
651    // bit-count is value-dependent too. `IsolateLowestOne` below uses the
652    // `self & (0 - self)` trick and IS branchless; callers needing a
653    // constant-time highest-bit isolation on a Ct carrier should mask
654    // through a different path.
655    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::IsolateHighestOne for FixedUInt<T, N, P> {
656        type Output = Self;
657        fn isolate_highest_one(self) -> Self {
658            let lz = <Self as const_num_traits::PrimBits>::leading_zeros(self);
659            if lz as usize == Self::BIT_SIZE {
660                // self == 0; preserve the zero.
661                <Self as const_num_traits::ConstZero>::ZERO
662            } else {
663                let pos = Self::BIT_SIZE as u32 - 1 - lz;
664                <Self as const_num_traits::ConstOne>::ONE << (pos as usize)
665            }
666        }
667    }
668
669    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::IsolateLowestOne for FixedUInt<T, N, P> {
670        type Output = Self;
671        fn isolate_lowest_one(self) -> Self {
672            // `self & (-self)`. For unsigned `-x` is `wrapping_neg(x) =
673            // (0).wrapping_sub(x)`. Works for `self == 0` (0 & 0 = 0).
674            let neg = <Self as const_num_traits::WrappingSub>::wrapping_sub(
675                <Self as const_num_traits::ConstZero>::ZERO,
676                self,
677            );
678            self & neg
679        }
680    }
681
682    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::IsolateHighestOne for &FixedUInt<T, N, P> {
683        type Output = FixedUInt<T, N, P>;
684        fn isolate_highest_one(self) -> FixedUInt<T, N, P> {
685            <FixedUInt<T, N, P> as const_num_traits::IsolateHighestOne>::isolate_highest_one(FixedUInt::from_array(self.array))
686        }
687    }
688
689    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::IsolateLowestOne for &FixedUInt<T, N, P> {
690        type Output = FixedUInt<T, N, P>;
691        fn isolate_lowest_one(self) -> FixedUInt<T, N, P> {
692            <FixedUInt<T, N, P> as const_num_traits::IsolateLowestOne>::isolate_lowest_one(FixedUInt::from_array(self.array))
693        }
694    }
695
696    // --- ShlExact / ShrExact -----------------------------------------------
697    //
698    // Reversible (lossless) shifts: return `None` if any one-bit would be
699    // shifted out, or `rhs >= BIT_SIZE`. Mirrors core's primitive impls
700    // exactly (compare `rhs` against `leading_zeros` / `trailing_zeros`).
701    //
702    // NOTE: NOT constant-time on a `FixedUInt<_, _, Ct>` carrier — the
703    // `Option` return shape leaks a range predicate on
704    // `leading_zeros(self)` / `trailing_zeros(self)`, i.e. the bit-width
705    // of the secret. Ct callers wanting exact shifts should either avoid
706    // this trait or gate on their own precondition before calling.
707
708    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::ShlExact for FixedUInt<T, N, P> {
709        type Output = FixedUInt<T, N, P>;
710        fn shl_exact(self, rhs: u32) -> Option<Self> {
711            if (rhs as usize) < Self::BIT_SIZE
712                && rhs <= <Self as const_num_traits::PrimBits>::leading_zeros(self)
713            {
714                Some(self << (rhs as usize))
715            } else {
716                None
717            }
718        }
719    }
720
721    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::ShrExact for FixedUInt<T, N, P> {
722        type Output = FixedUInt<T, N, P>;
723        fn shr_exact(self, rhs: u32) -> Option<Self> {
724            if (rhs as usize) < Self::BIT_SIZE
725                && rhs <= <Self as const_num_traits::PrimBits>::trailing_zeros(self)
726            {
727                Some(self >> (rhs as usize))
728            } else {
729                None
730            }
731        }
732    }
733
734    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::ShlExact for &FixedUInt<T, N, P> {
735        type Output = FixedUInt<T, N, P>;
736        fn shl_exact(self, rhs: u32) -> Option<FixedUInt<T, N, P>> {
737            <FixedUInt<T, N, P> as const_num_traits::ShlExact>::shl_exact(FixedUInt::from_array(self.array), rhs)
738        }
739    }
740
741    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::ShrExact for &FixedUInt<T, N, P> {
742        type Output = FixedUInt<T, N, P>;
743        fn shr_exact(self, rhs: u32) -> Option<FixedUInt<T, N, P>> {
744            <FixedUInt<T, N, P> as const_num_traits::ShrExact>::shr_exact(FixedUInt::from_array(self.array), rhs)
745        }
746    }
747
748    // --- FunnelShl / FunnelShr ---------------------------------------------
749    //
750    // Double-width funnel shift: form the conceptual `(hi, lo)` value of
751    // width `2 * BIT_SIZE`, shift by `n`, and return one half. `n` is a
752    // public parameter (loop counters, fixed amounts), so the `n >= BIT_SIZE`
753    // panic is value-independent and safe for both personalities. The shift
754    // ops dispatched by `<<` / `>>` are personality-aware (Ct uses the
755    // mask-AND-XOR variant), so the funnel impl inherits that.
756
757    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::FunnelShl for FixedUInt<T, N, P> {
758        type Output = Self;
759        fn funnel_shl(self, rhs: Self, n: u32) -> Self {
760            assert!((n as usize) < Self::BIT_SIZE, "FixedUInt::funnel_shl: n out of range");
761            if n == 0 {
762                self
763            } else {
764                let lo_shift = Self::BIT_SIZE as u32 - n;
765                (self << (n as usize)) | (rhs >> (lo_shift as usize))
766            }
767        }
768    }
769
770    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::FunnelShr for FixedUInt<T, N, P> {
771        type Output = Self;
772        fn funnel_shr(self, rhs: Self, n: u32) -> Self {
773            assert!((n as usize) < Self::BIT_SIZE, "FixedUInt::funnel_shr: n out of range");
774            if n == 0 {
775                rhs
776            } else {
777                let hi_shift = Self::BIT_SIZE as u32 - n;
778                (rhs >> (n as usize)) | (self << (hi_shift as usize))
779            }
780        }
781    }
782
783    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::FunnelShl for &FixedUInt<T, N, P> {
784        type Output = FixedUInt<T, N, P>;
785        fn funnel_shl(self, rhs: Self, n: u32) -> FixedUInt<T, N, P> {
786            <FixedUInt<T, N, P> as const_num_traits::FunnelShl>::funnel_shl(FixedUInt::from_array(self.array), FixedUInt::from_array(rhs.array), n)
787        }
788    }
789
790    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::FunnelShr for &FixedUInt<T, N, P> {
791        type Output = FixedUInt<T, N, P>;
792        fn funnel_shr(self, rhs: Self, n: u32) -> FixedUInt<T, N, P> {
793            <FixedUInt<T, N, P> as const_num_traits::FunnelShr>::funnel_shr(FixedUInt::from_array(self.array), FixedUInt::from_array(rhs.array), n)
794        }
795    }
796
797    // --- DepositBits / ExtractBits (PDEP / PEXT) ---------------------------
798    //
799    // Nct-only: the natural implementation iterates once per set bit of the
800    // mask, which is value-dependent. A constant-time version would have to
801    // iterate `BIT_SIZE` times unconditionally (and mask-select per step),
802    // which is a worthwhile but separate Ct-fixture exercise. For now we
803    // gate on `P = Nct`, matching how `CheckedDiv`/`CheckedRem` and the
804    // `Strict*` family are gated.
805
806    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> const_num_traits::DepositBits for FixedUInt<T, N, Nct> {
807        type Output = Self;
808        fn deposit_bits(self, mask: Self) -> Self {
809            // Scatter contiguous low bits of `self` into positions of the
810            // one-bits of `mask`. Iterates once per set bit of `mask`.
811            let mut result = <Self as const_num_traits::ConstZero>::ZERO;
812            let mut remaining = mask;
813            let mut bb = <Self as const_num_traits::ConstOne>::ONE;
814            while !<Self as const_num_traits::Zero>::is_zero(&remaining) {
815                // Lowest set bit of `remaining` via `x & -x`.
816                let lowest = <Self as const_num_traits::IsolateLowestOne>::isolate_lowest_one(remaining);
817                if !<Self as const_num_traits::Zero>::is_zero(&(self & bb)) {
818                    result |= lowest;
819                }
820                remaining = remaining & <Self as const_num_traits::WrappingSub>::wrapping_sub(
821                    remaining,
822                    <Self as const_num_traits::ConstOne>::ONE,
823                );
824                bb = <Self as const_num_traits::WrappingShl>::wrapping_shl(bb, 1);
825            }
826            result
827        }
828    }
829
830    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> const_num_traits::ExtractBits for FixedUInt<T, N, Nct> {
831        type Output = Self;
832        fn extract_bits(self, mask: Self) -> Self {
833            // Gather the bits of `self` selected by `mask` into the low end
834            // of the result. Mirror of `deposit_bits`.
835            let mut result = <Self as const_num_traits::ConstZero>::ZERO;
836            let mut remaining = mask;
837            let mut bb = <Self as const_num_traits::ConstOne>::ONE;
838            while !<Self as const_num_traits::Zero>::is_zero(&remaining) {
839                let lowest = <Self as const_num_traits::IsolateLowestOne>::isolate_lowest_one(remaining);
840                if !<Self as const_num_traits::Zero>::is_zero(&(self & lowest)) {
841                    result |= bb;
842                }
843                remaining = remaining & <Self as const_num_traits::WrappingSub>::wrapping_sub(
844                    remaining,
845                    <Self as const_num_traits::ConstOne>::ONE,
846                );
847                bb = <Self as const_num_traits::WrappingShl>::wrapping_shl(bb, 1);
848            }
849            result
850        }
851    }
852
853    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> const_num_traits::DepositBits for &FixedUInt<T, N, Nct> {
854        type Output = FixedUInt<T, N, Nct>;
855        fn deposit_bits(self, mask: Self) -> FixedUInt<T, N, Nct> {
856            <FixedUInt<T, N, Nct> as const_num_traits::DepositBits>::deposit_bits(FixedUInt::from_array(self.array), FixedUInt::from_array(mask.array))
857        }
858    }
859
860    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> const_num_traits::ExtractBits for &FixedUInt<T, N, Nct> {
861        type Output = FixedUInt<T, N, Nct>;
862        fn extract_bits(self, mask: Self) -> FixedUInt<T, N, Nct> {
863            <FixedUInt<T, N, Nct> as const_num_traits::ExtractBits>::extract_bits(FixedUInt::from_array(self.array), FixedUInt::from_array(mask.array))
864        }
865    }
866}
867
868// num_traits wrappers - delegate to const impls
869#[cfg(feature = "num-traits")]
870impl<T: MachineWord, const N: usize, P: Personality> num_traits::WrappingShl
871    for FixedUInt<T, N, P>
872{
873    fn wrapping_shl(&self, bits: u32) -> Self {
874        <&Self as WrappingShl>::wrapping_shl(self, bits)
875    }
876}
877
878#[cfg(feature = "num-traits")]
879impl<T: MachineWord, const N: usize, P: Personality> num_traits::WrappingShr
880    for FixedUInt<T, N, P>
881{
882    fn wrapping_shr(&self, bits: u32) -> Self {
883        <&Self as WrappingShr>::wrapping_shr(self, bits)
884    }
885}
886
887#[cfg(feature = "num-traits")]
888impl<T: MachineWord, const N: usize, P: Personality> num_traits::CheckedShl for FixedUInt<T, N, P> {
889    fn checked_shl(&self, bits: u32) -> Option<Self> {
890        <&Self as CheckedShl>::checked_shl(self, bits)
891    }
892}
893
894#[cfg(feature = "num-traits")]
895impl<T: MachineWord, const N: usize, P: Personality> num_traits::CheckedShr for FixedUInt<T, N, P> {
896    fn checked_shr(&self, bits: u32) -> Option<Self> {
897        <&Self as CheckedShr>::checked_shr(self, bits)
898    }
899}
900
901#[cfg(test)]
902// Coverage tests deliberately exercise every ref/value combination of
903// the bitwise/shift operators (see `test_*_combinations`).
904#[allow(clippy::op_ref)]
905mod tests {
906    use super::*;
907
908    #[test]
909    fn test_bitand_combinations() {
910        let a = FixedUInt::<u8, 2>::from(12u8); // 1100
911        let b = FixedUInt::<u8, 2>::from(10u8); // 1010
912        let expected = FixedUInt::<u8, 2>::from(8u8); // 1000
913
914        // value & value
915        assert_eq!(a & b, expected);
916        // value & ref
917        assert_eq!(a & &b, expected);
918        // ref & value
919        assert_eq!(&a & b, expected);
920        // ref & ref
921        assert_eq!(&a & &b, expected);
922    }
923
924    #[test]
925    fn test_bitor_combinations() {
926        let a = FixedUInt::<u8, 2>::from(12u8); // 1100
927        let b = FixedUInt::<u8, 2>::from(10u8); // 1010
928        let expected = FixedUInt::<u8, 2>::from(14u8); // 1110
929
930        // value | value
931        assert_eq!(a | b, expected);
932        // value | ref
933        assert_eq!(a | &b, expected);
934        // ref | value
935        assert_eq!(&a | b, expected);
936        // ref | ref
937        assert_eq!(&a | &b, expected);
938    }
939
940    #[test]
941    fn test_bitxor_combinations() {
942        let a = FixedUInt::<u8, 2>::from(12u8); // 1100
943        let b = FixedUInt::<u8, 2>::from(10u8); // 1010
944        let expected = FixedUInt::<u8, 2>::from(6u8); // 0110
945
946        // value ^ value
947        assert_eq!(a ^ b, expected);
948        // value ^ ref
949        assert_eq!(a ^ &b, expected);
950        // ref ^ value
951        assert_eq!(&a ^ b, expected);
952        // ref ^ ref
953        assert_eq!(&a ^ &b, expected);
954    }
955
956    #[test]
957    fn test_shl_combinations() {
958        let a = FixedUInt::<u8, 2>::from(2u8); // 0010
959        let shift: usize = 2;
960        let expected = FixedUInt::<u8, 2>::from(8u8); // 1000
961
962        // value << value
963        assert_eq!(a << shift, expected);
964        // value << ref
965        assert_eq!(a << &shift, expected);
966        // ref << value
967        assert_eq!(&a << shift, expected);
968        // ref << ref
969        assert_eq!(&a << &shift, expected);
970
971        // Same with u32
972        let shift32: u32 = 2;
973        assert_eq!(a << shift32, expected);
974        assert_eq!(a << &shift32, expected);
975        assert_eq!(&a << shift32, expected);
976        assert_eq!(&a << &shift32, expected);
977    }
978
979    #[test]
980    fn test_shr_combinations() {
981        let a = FixedUInt::<u8, 2>::from(8u8); // 1000
982        let shift: usize = 2;
983        let expected = FixedUInt::<u8, 2>::from(2u8); // 0010
984
985        // value >> value
986        assert_eq!(a >> shift, expected);
987        // value >> ref
988        assert_eq!(a >> &shift, expected);
989        // ref >> value
990        assert_eq!(&a >> shift, expected);
991        // ref >> ref
992        assert_eq!(&a >> &shift, expected);
993
994        // Same with u32
995        let shift32: u32 = 2;
996        assert_eq!(a >> shift32, expected);
997        assert_eq!(a >> &shift32, expected);
998        assert_eq!(&a >> shift32, expected);
999        assert_eq!(&a >> &shift32, expected);
1000    }
1001
1002    #[test]
1003    fn test_const_bitops() {
1004        type TestInt = FixedUInt<u8, 2>;
1005
1006        let a = TestInt::from(0b11001100u8);
1007        let b = TestInt::from(0b10101010u8);
1008
1009        // Test not
1010        let not_a = !a;
1011        assert_eq!(not_a.array[0], 0b00110011);
1012        assert_eq!(not_a.array[1], 0xFF);
1013
1014        // Test bitand
1015        assert_eq!(a & b, TestInt::from(0b10001000u8));
1016
1017        // Test bitor
1018        assert_eq!(a | b, TestInt::from(0b11101110u8));
1019
1020        // Test bitxor
1021        assert_eq!(a ^ b, TestInt::from(0b01100110u8));
1022
1023        // Test shl
1024        assert_eq!(TestInt::from(1u8) << 4usize, TestInt::from(16u8));
1025
1026        // Test shr
1027        assert_eq!(TestInt::from(16u8) >> 2usize, TestInt::from(4u8));
1028
1029        #[cfg(feature = "nightly")]
1030        {
1031            const A: TestInt = FixedUInt::from_array([0b11001100, 0]);
1032            const B: TestInt = FixedUInt::from_array([0b10101010, 0]);
1033
1034            const NOT_A: TestInt = !A;
1035            const AND_AB: TestInt = A & B;
1036            const OR_AB: TestInt = A | B;
1037            const XOR_AB: TestInt = A ^ B;
1038            const SHL_1: TestInt = FixedUInt::from_array([1u8, 0]) << 4usize;
1039            const SHR_16: TestInt = FixedUInt::from_array([16u8, 0]) >> 2usize;
1040
1041            assert_eq!(NOT_A.array[0], 0b00110011);
1042            assert_eq!(AND_AB.array[0], 0b10001000);
1043            assert_eq!(OR_AB.array[0], 0b11101110);
1044            assert_eq!(XOR_AB.array[0], 0b01100110);
1045            assert_eq!(SHL_1.array[0], 16);
1046            assert_eq!(SHR_16.array[0], 4);
1047        }
1048    }
1049
1050    #[test]
1051    fn test_const_shift_traits() {
1052        type TestInt = FixedUInt<u8, 2>; // 16-bit
1053
1054        // Test overflowing_shl
1055        let a = TestInt::from(0x80u8); // 0x0080
1056        let (res, overflow) = OverflowingShl::overflowing_shl(a, 8);
1057        assert_eq!(res.array, [0, 0x80]); // 0x8000
1058        assert!(!overflow);
1059
1060        let (res, overflow) = OverflowingShl::overflowing_shl(a, 16);
1061        assert_eq!(res.array, [0x80, 0]); // wraps around
1062        assert!(overflow);
1063
1064        let (res, overflow) = OverflowingShl::overflowing_shl(a, 9);
1065        assert_eq!(res.array, [0, 0]); // high bits shifted out (but shift < bit_width)
1066        assert!(!overflow); // 9 < 16, so no overflow
1067
1068        // Test overflowing_shr
1069        let b = TestInt::from(0x0100u16); // 0x0100
1070        let (res, overflow) = OverflowingShr::overflowing_shr(b, 8);
1071        assert_eq!(res.array, [1, 0]); // 0x0001
1072        assert!(!overflow);
1073
1074        let (res, overflow) = OverflowingShr::overflowing_shr(b, 16);
1075        assert_eq!(res.array, [0, 1]); // wraps
1076        assert!(overflow);
1077
1078        // Test wrapping_shl
1079        let c = TestInt::from(1u8);
1080        assert_eq!(WrappingShl::wrapping_shl(c, 4).array, [16, 0]);
1081        assert_eq!(WrappingShl::wrapping_shl(c, 16).array, [1, 0]); // wraps
1082        assert_eq!(WrappingShl::wrapping_shl(c, 17).array, [2, 0]); // wraps
1083
1084        // Test wrapping_shr
1085        let d = TestInt::from(0x8000u16);
1086        assert_eq!(WrappingShr::wrapping_shr(d, 4).array, [0, 0x08]);
1087        assert_eq!(WrappingShr::wrapping_shr(d, 16).array, [0, 0x80]); // wraps
1088        assert_eq!(WrappingShr::wrapping_shr(d, 17).array, [0, 0x40]); // wraps
1089
1090        // Test checked_shl
1091        let e = TestInt::from(1u8);
1092        assert_eq!(CheckedShl::checked_shl(e, 4), Some(TestInt::from(16u8)));
1093        assert_eq!(
1094            CheckedShl::checked_shl(e, 15),
1095            Some(TestInt::from(0x8000u16))
1096        );
1097        assert_eq!(CheckedShl::checked_shl(e, 16), None); // overflow
1098
1099        // Test checked_shr
1100        let f = TestInt::from(0x8000u16);
1101        assert_eq!(CheckedShr::checked_shr(f, 15), Some(TestInt::from(1u8)));
1102        assert_eq!(CheckedShr::checked_shr(f, 16), None); // overflow
1103
1104        // Test edge case: zero shift
1105        let g = TestInt::from(42u8);
1106        assert_eq!(OverflowingShl::overflowing_shl(g, 0), (g, false));
1107        assert_eq!(OverflowingShr::overflowing_shr(g, 0), (g, false));
1108        assert_eq!(WrappingShl::wrapping_shl(g, 0), g);
1109        assert_eq!(WrappingShr::wrapping_shr(g, 0), g);
1110        assert_eq!(CheckedShl::checked_shl(g, 0), Some(g));
1111        assert_eq!(CheckedShr::checked_shr(g, 0), Some(g));
1112    }
1113
1114    #[test]
1115    fn test_const_shift_traits_n0() {
1116        // Test with N=0 (zero-sized type)
1117        type ZeroInt = FixedUInt<u8, 0>;
1118        let z = ZeroInt::from_array([]);
1119
1120        // All shifts on zero-sized type should overflow
1121        assert_eq!(OverflowingShl::overflowing_shl(z, 0), (z, true));
1122        assert_eq!(OverflowingShr::overflowing_shr(z, 0), (z, true));
1123        assert_eq!(WrappingShl::wrapping_shl(z, 0), z);
1124        assert_eq!(WrappingShr::wrapping_shr(z, 0), z);
1125        assert_eq!(CheckedShl::checked_shl(z, 0), None);
1126        assert_eq!(CheckedShr::checked_shr(z, 0), None);
1127    }
1128
1129    #[test]
1130    #[cfg(feature = "num-traits")]
1131    fn test_num_traits_shift_wrappers() {
1132        use num_traits::{CheckedShl, CheckedShr, WrappingShl, WrappingShr};
1133
1134        type TestInt = FixedUInt<u8, 2>;
1135
1136        let a = TestInt::from(1u8);
1137
1138        // num_traits::WrappingShl is by-ref (upstream signature).
1139        assert_eq!(WrappingShl::wrapping_shl(&a, 4), TestInt::from(16u8));
1140        assert_eq!(WrappingShl::wrapping_shl(&a, 16), a); // wraps
1141
1142        // num_traits::WrappingShr
1143        let b = TestInt::from(16u8);
1144        assert_eq!(WrappingShr::wrapping_shr(&b, 4), TestInt::from(1u8));
1145
1146        // num_traits::CheckedShl
1147        assert_eq!(CheckedShl::checked_shl(&a, 4), Some(TestInt::from(16u8)));
1148        assert_eq!(CheckedShl::checked_shl(&a, 16), None);
1149
1150        // num_traits::CheckedShr
1151        assert_eq!(CheckedShr::checked_shr(&b, 4), Some(TestInt::from(1u8)));
1152        assert_eq!(CheckedShr::checked_shr(&b, 16), None);
1153    }
1154
1155    #[test]
1156    fn test_unbounded_shift() {
1157        type U16 = FixedUInt<u8, 2>;
1158
1159        let one = U16::from(1u8);
1160
1161        // Normal shifts (within bounds)
1162        assert_eq!(UnboundedShl::unbounded_shl(one, 0), one);
1163        assert_eq!(UnboundedShl::unbounded_shl(one, 4), U16::from(16u8));
1164        assert_eq!(UnboundedShl::unbounded_shl(one, 15), U16::from(0x8000u16));
1165
1166        assert_eq!(UnboundedShr::unbounded_shr(U16::from(0x8000u16), 15), one);
1167        assert_eq!(UnboundedShr::unbounded_shr(U16::from(16u8), 4), one);
1168
1169        // At boundary (shift by bit width) - returns 0
1170        assert_eq!(UnboundedShl::unbounded_shl(one, 16), U16::from(0u8));
1171        assert_eq!(
1172            UnboundedShr::unbounded_shr(U16::from(0xFFFFu16), 16),
1173            U16::from(0u8)
1174        );
1175
1176        // Beyond boundary - returns 0
1177        assert_eq!(
1178            UnboundedShl::unbounded_shl(U16::from(0xFFFFu16), 17),
1179            U16::from(0u8)
1180        );
1181        assert_eq!(
1182            UnboundedShl::unbounded_shl(U16::from(0xFFFFu16), 100),
1183            U16::from(0u8)
1184        );
1185        assert_eq!(
1186            UnboundedShr::unbounded_shr(U16::from(0xFFFFu16), 17),
1187            U16::from(0u8)
1188        );
1189        assert_eq!(
1190            UnboundedShr::unbounded_shr(U16::from(0xFFFFu16), 100),
1191            U16::from(0u8)
1192        );
1193
1194        // Test with different word sizes
1195        type U32 = FixedUInt<u8, 4>;
1196        let one32 = U32::from(1u8);
1197        assert_eq!(
1198            UnboundedShl::unbounded_shl(one32, 31),
1199            U32::from(0x80000000u32)
1200        );
1201        assert_eq!(UnboundedShl::unbounded_shl(one32, 32), U32::from(0u8));
1202        assert_eq!(
1203            UnboundedShr::unbounded_shr(U32::from(0x80000000u32), 31),
1204            one32
1205        );
1206        assert_eq!(
1207            UnboundedShr::unbounded_shr(U32::from(0x80000000u32), 32),
1208            U32::from(0u8)
1209        );
1210    }
1211
1212    #[test]
1213    fn test_unbounded_shift_polymorphic() {
1214        fn test_unbounded<T>(val: T, shift: u32, expected_shl: T, expected_shr: T)
1215        where
1216            T: UnboundedShl<Output = T> + UnboundedShr<Output = T> + Eq + core::fmt::Debug + Copy,
1217        {
1218            assert_eq!(UnboundedShl::unbounded_shl(val, shift), expected_shl);
1219            assert_eq!(UnboundedShr::unbounded_shr(val, shift), expected_shr);
1220        }
1221
1222        // Test with FixedUInt layouts
1223        type U8x2 = FixedUInt<u8, 2>;
1224        type U8x4 = FixedUInt<u8, 4>;
1225        type U16x2 = FixedUInt<u16, 2>;
1226
1227        // Same logical shift, different layouts
1228        test_unbounded(U8x2::from(1u8), 4, U8x2::from(16u8), U8x2::from(0u8));
1229        test_unbounded(U8x4::from(1u8), 4, U8x4::from(16u8), U8x4::from(0u8));
1230        test_unbounded(U16x2::from(1u8), 4, U16x2::from(16u8), U16x2::from(0u8));
1231
1232        // Test with primitives
1233        test_unbounded(1u8, 4, 16u8, 0u8);
1234        test_unbounded(1u16, 4, 16u16, 0u16);
1235        test_unbounded(1u32, 4, 16u32, 0u32);
1236
1237        // Boundary tests
1238        test_unbounded(1u8, 8, 0u8, 0u8);
1239        test_unbounded(U8x2::from(1u8), 16, U8x2::from(0u8), U8x2::from(0u8));
1240    }
1241
1242    #[test]
1243    fn test_bit_width() {
1244        use const_num_traits::BitWidth;
1245        type U16 = FixedUInt<u8, 2>;
1246        assert_eq!(BitWidth::bit_width(U16::from(0u8)), 0);
1247        assert_eq!(BitWidth::bit_width(U16::from(1u8)), 1);
1248        assert_eq!(BitWidth::bit_width(U16::from(2u8)), 2);
1249        assert_eq!(BitWidth::bit_width(U16::from(3u8)), 2);
1250        assert_eq!(BitWidth::bit_width(U16::from(255u8)), 8);
1251        assert_eq!(BitWidth::bit_width(U16::from(256u16)), 9);
1252        assert_eq!(BitWidth::bit_width(U16::from(0xFFFFu16)), 16);
1253    }
1254
1255    #[test]
1256    fn test_bits_precision() {
1257        use const_num_traits::BitsPrecision;
1258        // Fixed carrier: width == BIT_SIZE, value-independent (= capacity).
1259        type U16 = FixedUInt<u8, 2>;
1260        type U32 = FixedUInt<u32, 1>;
1261        assert_eq!(BitsPrecision::bits_precision(&U16::from(0u8)), 16);
1262        assert_eq!(BitsPrecision::bits_precision(&U16::from(0xFFFFu16)), 16);
1263        assert_eq!(BitsPrecision::bits_precision(&U32::from(0u8)), 32);
1264        assert_eq!(BitsPrecision::bits_precision(&U32::from(7u8)), 32);
1265    }
1266
1267    #[test]
1268    fn test_highest_lowest_one() {
1269        use const_num_traits::{HighestOne, LowestOne};
1270        type U16 = FixedUInt<u8, 2>;
1271        assert_eq!(HighestOne::highest_one(U16::from(0u8)), None);
1272        assert_eq!(HighestOne::highest_one(U16::from(1u8)), Some(0));
1273        assert_eq!(HighestOne::highest_one(U16::from(0b1010_0000u8)), Some(7));
1274        assert_eq!(HighestOne::highest_one(U16::from(0x8000u16)), Some(15));
1275
1276        assert_eq!(LowestOne::lowest_one(U16::from(0u8)), None);
1277        assert_eq!(LowestOne::lowest_one(U16::from(1u8)), Some(0));
1278        assert_eq!(LowestOne::lowest_one(U16::from(0b0010_1000u8)), Some(3));
1279        assert_eq!(LowestOne::lowest_one(U16::from(0x8000u16)), Some(15));
1280    }
1281
1282    #[test]
1283    fn test_isolate_highest_lowest_one() {
1284        use const_num_traits::{IsolateHighestOne, IsolateLowestOne};
1285        type U16 = FixedUInt<u8, 2>;
1286        // zero → zero
1287        assert_eq!(
1288            IsolateHighestOne::isolate_highest_one(U16::from(0u8)),
1289            U16::from(0u8)
1290        );
1291        assert_eq!(
1292            IsolateLowestOne::isolate_lowest_one(U16::from(0u8)),
1293            U16::from(0u8)
1294        );
1295        // nonzero
1296        assert_eq!(
1297            IsolateHighestOne::isolate_highest_one(U16::from(0b1010_0000u8)),
1298            U16::from(0b1000_0000u8)
1299        );
1300        assert_eq!(
1301            IsolateLowestOne::isolate_lowest_one(U16::from(0b1010_1000u8)),
1302            U16::from(0b0000_1000u8)
1303        );
1304        // power of two: highest == lowest == self
1305        let p: U16 = U16::from(0x0100u16);
1306        assert_eq!(IsolateHighestOne::isolate_highest_one(p), p);
1307        assert_eq!(IsolateLowestOne::isolate_lowest_one(p), p);
1308    }
1309
1310    #[test]
1311    fn test_shl_shr_exact() {
1312        use const_num_traits::{ShlExact, ShrExact};
1313        type U16 = FixedUInt<u8, 2>;
1314        // shl_exact: must not lose bits
1315        assert_eq!(
1316            ShlExact::shl_exact(U16::from(1u8), 4),
1317            Some(U16::from(16u8))
1318        );
1319        assert_eq!(ShlExact::shl_exact(U16::from(0u8), 8), Some(U16::from(0u8)));
1320        // dropping a high bit → None
1321        assert_eq!(ShlExact::shl_exact(U16::from(0x8000u16), 1), None);
1322        // rhs >= BIT_SIZE → None
1323        assert_eq!(ShlExact::shl_exact(U16::from(1u8), 16), None);
1324
1325        // shr_exact: must not lose set bits
1326        assert_eq!(
1327            ShrExact::shr_exact(U16::from(16u8), 4),
1328            Some(U16::from(1u8))
1329        );
1330        assert_eq!(ShrExact::shr_exact(U16::from(0u8), 8), Some(U16::from(0u8)));
1331        // dropping a low bit → None
1332        assert_eq!(ShrExact::shr_exact(U16::from(0b0001u8), 1), None);
1333        assert_eq!(ShrExact::shr_exact(U16::from(0b0011u8), 1), None);
1334        // rhs >= BIT_SIZE → None
1335        assert_eq!(ShrExact::shr_exact(U16::from(1u8), 16), None);
1336    }
1337
1338    #[test]
1339    #[allow(clippy::needless_borrows_for_generic_args)]
1340    fn test_ref_receivers_compile_through() {
1341        use const_num_traits::{BitWidth, IsolateHighestOne, IsolateLowestOne, ShlExact, ShrExact};
1342        type U16 = FixedUInt<u8, 2>;
1343        let v = U16::from(0b0010_1000u8);
1344        assert_eq!(BitWidth::bit_width(&v), 6);
1345        assert_eq!(
1346            IsolateHighestOne::isolate_highest_one(&v),
1347            U16::from(0b0010_0000u8)
1348        );
1349        assert_eq!(
1350            IsolateLowestOne::isolate_lowest_one(&v),
1351            U16::from(0b0000_1000u8)
1352        );
1353        assert_eq!(ShlExact::shl_exact(&v, 2), Some(U16::from(0b1010_0000u8)));
1354        assert_eq!(ShrExact::shr_exact(&v, 3), Some(U16::from(0b0000_0101u8)));
1355    }
1356
1357    #[test]
1358    fn test_funnel_shifts() {
1359        use const_num_traits::{FunnelShl, FunnelShr};
1360        type U16 = FixedUInt<u8, 2>;
1361
1362        // 0x0001_8000 << 1 = 0x0003_0000; high half = 0x0003.
1363        assert_eq!(
1364            FunnelShl::funnel_shl(U16::from(0x0001u16), U16::from(0x8000u16), 1),
1365            U16::from(0x0003u16),
1366        );
1367        // n == 0: returns self (hi)
1368        assert_eq!(
1369            FunnelShl::funnel_shl(U16::from(0xABCDu16), U16::from(0xFFFFu16), 0),
1370            U16::from(0xABCDu16),
1371        );
1372        // 0x0001_8000 >> 1 = 0x0000_C000; low half = 0xC000.
1373        assert_eq!(
1374            FunnelShr::funnel_shr(U16::from(0x0001u16), U16::from(0x8000u16), 1),
1375            U16::from(0xC000u16),
1376        );
1377        // n == 0: returns rhs (lo)
1378        assert_eq!(
1379            FunnelShr::funnel_shr(U16::from(0xABCDu16), U16::from(0x1234u16), 0),
1380            U16::from(0x1234u16),
1381        );
1382
1383        // Reference receivers
1384        let hi = U16::from(0x0001u16);
1385        let lo = U16::from(0x8000u16);
1386        assert_eq!(FunnelShl::funnel_shl(&hi, &lo, 1), U16::from(0x0003u16));
1387        assert_eq!(FunnelShr::funnel_shr(&hi, &lo, 1), U16::from(0xC000u16));
1388    }
1389
1390    #[test]
1391    #[should_panic(expected = "funnel_shl: n out of range")]
1392    fn test_funnel_shl_panics_at_bit_size() {
1393        use const_num_traits::FunnelShl;
1394        type U16 = FixedUInt<u8, 2>;
1395        let _ = FunnelShl::funnel_shl(U16::from(1u8), U16::from(0u8), 16);
1396    }
1397
1398    #[test]
1399    fn test_deposit_extract_bits() {
1400        use const_num_traits::Nct;
1401        use const_num_traits::{DepositBits, ExtractBits};
1402        type U16 = FixedUInt<u8, 2, Nct>;
1403
1404        // Mirror of the primitive doctest:
1405        // deposit_bits(0b101, mask=0b1111_0000) = 0b0101_0000
1406        assert_eq!(
1407            DepositBits::deposit_bits(U16::from(0b101u8), U16::from(0b1111_0000u8)),
1408            U16::from(0b0101_0000u8),
1409        );
1410        // extract_bits(0b0101_0011, mask=0b1111_0000) = 0b101
1411        assert_eq!(
1412            ExtractBits::extract_bits(U16::from(0b0101_0011u8), U16::from(0b1111_0000u8)),
1413            U16::from(0b101u8),
1414        );
1415
1416        // Empty mask → 0 (both directions).
1417        assert_eq!(
1418            DepositBits::deposit_bits(U16::from(0xFFFFu16), U16::from(0u8)),
1419            U16::from(0u8),
1420        );
1421        assert_eq!(
1422            ExtractBits::extract_bits(U16::from(0xFFFFu16), U16::from(0u8)),
1423            U16::from(0u8),
1424        );
1425
1426        // All-ones mask → identity.
1427        assert_eq!(
1428            DepositBits::deposit_bits(U16::from(0xABCDu16), U16::from(0xFFFFu16)),
1429            U16::from(0xABCDu16),
1430        );
1431        assert_eq!(
1432            ExtractBits::extract_bits(U16::from(0xABCDu16), U16::from(0xFFFFu16)),
1433            U16::from(0xABCDu16),
1434        );
1435
1436        // Round-trip on a non-trivial mask. extract then deposit through the
1437        // same mask gives back the originally-selected bits in their original
1438        // positions.
1439        let mask = U16::from(0b1010_1010u8);
1440        let v = U16::from(0b1111_1111u8);
1441        let extracted = ExtractBits::extract_bits(v, mask);
1442        let redeposited = DepositBits::deposit_bits(extracted, mask);
1443        assert_eq!(redeposited, v & mask);
1444
1445        // Reference receivers
1446        let v_ref = U16::from(0b0110_0110u8);
1447        let m_ref = U16::from(0b1111_0000u8);
1448        assert_eq!(
1449            ExtractBits::extract_bits(&v_ref, &m_ref),
1450            U16::from(0b0110u8),
1451        );
1452        assert_eq!(
1453            DepositBits::deposit_bits(&U16::from(0b101u8), &m_ref),
1454            U16::from(0b0101_0000u8),
1455        );
1456    }
1457
1458    // --- Empirical const-evaluability proofs ---------------------------------
1459    //
1460    // Each trait method below is invoked from a `c0nst fn` wrapper, so the
1461    // surrounding `c0nst::c0nst!` block forces the compiler to treat it as
1462    // const-callable when the `nightly` feature is enabled. The
1463    // `nightly_const_eval_*` tests then bind the wrapper's result to a
1464    // `const` item — proving the trait method actually evaluates at compile
1465    // time, not just that the impl is annotated `c0nst`.
1466
1467    c0nst::c0nst! {
1468        pub c0nst fn const_overflowing_shl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> (FixedUInt<T, N, P>, bool) {
1469            OverflowingShl::overflowing_shl(v, bits)
1470        }
1471        pub c0nst fn const_overflowing_shr<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> (FixedUInt<T, N, P>, bool) {
1472            OverflowingShr::overflowing_shr(v, bits)
1473        }
1474        pub c0nst fn const_wrapping_shl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> FixedUInt<T, N, P> {
1475            WrappingShl::wrapping_shl(v, bits)
1476        }
1477        pub c0nst fn const_wrapping_shr<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> FixedUInt<T, N, P> {
1478            WrappingShr::wrapping_shr(v, bits)
1479        }
1480        pub c0nst fn const_checked_shl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> Option<FixedUInt<T, N, P>> {
1481            CheckedShl::checked_shl(v, bits)
1482        }
1483        pub c0nst fn const_checked_shr<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> Option<FixedUInt<T, N, P>> {
1484            CheckedShr::checked_shr(v, bits)
1485        }
1486        pub c0nst fn const_unbounded_shl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> FixedUInt<T, N, P> {
1487            UnboundedShl::unbounded_shl(v, bits)
1488        }
1489        pub c0nst fn const_unbounded_shr<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> FixedUInt<T, N, P> {
1490            UnboundedShr::unbounded_shr(v, bits)
1491        }
1492        pub c0nst fn const_highest_one<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>) -> Option<u32> {
1493            const_num_traits::HighestOne::highest_one(v)
1494        }
1495        pub c0nst fn const_lowest_one<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>) -> Option<u32> {
1496            const_num_traits::LowestOne::lowest_one(v)
1497        }
1498        pub c0nst fn const_bit_width<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>) -> u32 {
1499            const_num_traits::BitWidth::bit_width(v)
1500        }
1501        pub c0nst fn const_isolate_highest_one<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>) -> FixedUInt<T, N, P> {
1502            const_num_traits::IsolateHighestOne::isolate_highest_one(v)
1503        }
1504        pub c0nst fn const_isolate_lowest_one<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>) -> FixedUInt<T, N, P> {
1505            const_num_traits::IsolateLowestOne::isolate_lowest_one(v)
1506        }
1507        pub c0nst fn const_shl_exact<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> Option<FixedUInt<T, N, P>> {
1508            const_num_traits::ShlExact::shl_exact(v, bits)
1509        }
1510        pub c0nst fn const_shr_exact<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>, bits: u32) -> Option<FixedUInt<T, N, P>> {
1511            const_num_traits::ShrExact::shr_exact(v, bits)
1512        }
1513        pub c0nst fn const_funnel_shl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(hi: FixedUInt<T, N, P>, lo: FixedUInt<T, N, P>, n: u32) -> FixedUInt<T, N, P> {
1514            const_num_traits::FunnelShl::funnel_shl(hi, lo, n)
1515        }
1516        pub c0nst fn const_funnel_shr<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(hi: FixedUInt<T, N, P>, lo: FixedUInt<T, N, P>, n: u32) -> FixedUInt<T, N, P> {
1517            const_num_traits::FunnelShr::funnel_shr(hi, lo, n)
1518        }
1519        pub c0nst fn const_deposit_bits<T: [c0nst] ConstMachineWord + MachineWord, const N: usize>(v: FixedUInt<T, N, Nct>, mask: FixedUInt<T, N, Nct>) -> FixedUInt<T, N, Nct> {
1520            const_num_traits::DepositBits::deposit_bits(v, mask)
1521        }
1522        pub c0nst fn const_extract_bits<T: [c0nst] ConstMachineWord + MachineWord, const N: usize>(v: FixedUInt<T, N, Nct>, mask: FixedUInt<T, N, Nct>) -> FixedUInt<T, N, Nct> {
1523            const_num_traits::ExtractBits::extract_bits(v, mask)
1524        }
1525    }
1526
1527    #[test]
1528    fn nightly_const_eval_bit_traits() {
1529        type U16 = FixedUInt<u8, 2>;
1530
1531        // Runtime smoke — the wrappers themselves work.
1532        let v = U16::from(1u8);
1533        assert_eq!(const_overflowing_shl(v, 4), (U16::from(16u8), false));
1534        assert_eq!(const_wrapping_shl(v, 4), U16::from(16u8));
1535        assert_eq!(const_checked_shl(v, 16), None);
1536        assert_eq!(const_bit_width(U16::from(0xFFu8)), 8);
1537
1538        // The real proof — evaluate at compile time.
1539        #[cfg(feature = "nightly")]
1540        {
1541            const V: U16 = FixedUInt::from_array([1, 0]);
1542            const V_FF: U16 = FixedUInt::from_array([0xFF, 0]);
1543            const V_MASK: U16 = FixedUInt::from_array([0b1010_1000, 0]);
1544            const HI: U16 = FixedUInt::from_array([1, 0]);
1545            const LO: U16 = FixedUInt::from_array([0, 0x80]);
1546
1547            const OSHL: (U16, bool) = const_overflowing_shl(V, 4);
1548            const OSHR: (U16, bool) = const_overflowing_shr(V_FF, 4);
1549            const WSHL: U16 = const_wrapping_shl(V, 4);
1550            const WSHR: U16 = const_wrapping_shr(V_FF, 4);
1551            const CSHL: Option<U16> = const_checked_shl(V, 16);
1552            const CSHR: Option<U16> = const_checked_shr(V, 4);
1553            const USHL: U16 = const_unbounded_shl(V, 8);
1554            const USHR: U16 = const_unbounded_shr(V_FF, 4);
1555            const HI_ONE: Option<u32> = const_highest_one(V_FF);
1556            const LO_ONE: Option<u32> = const_lowest_one(V_MASK);
1557            const BW: u32 = const_bit_width(V_FF);
1558            const IH: U16 = const_isolate_highest_one(V_MASK);
1559            const IL: U16 = const_isolate_lowest_one(V_MASK);
1560            const SHLEX: Option<U16> = const_shl_exact(V, 4);
1561            const SHREX: Option<U16> = const_shr_exact(FixedUInt::from_array([16, 0]), 4);
1562            const FSHL: U16 = const_funnel_shl(HI, LO, 1);
1563            const FSHR: U16 = const_funnel_shr(HI, LO, 1);
1564            const DEP: U16 = const_deposit_bits(
1565                FixedUInt::from_array([0b101, 0]),
1566                FixedUInt::from_array([0b1111_0000, 0]),
1567            );
1568            const EXT: U16 = const_extract_bits(
1569                FixedUInt::from_array([0b0101_0011, 0]),
1570                FixedUInt::from_array([0b1111_0000, 0]),
1571            );
1572
1573            // Sanity-check a representative subset of the const results.
1574            assert_eq!(OSHL.0.array, [16, 0]);
1575            assert!(!OSHL.1);
1576            assert_eq!(OSHR.0.array, [0x0F, 0]);
1577            assert!(!OSHR.1);
1578            assert_eq!(WSHL.array, [16, 0]);
1579            assert_eq!(WSHR.array, [0x0F, 0]);
1580            assert!(CSHL.is_none());
1581            assert!(CSHR.is_some());
1582            assert_eq!(USHL.array, [0, 1]);
1583            assert_eq!(USHR.array, [0x0F, 0]);
1584            assert_eq!(HI_ONE, Some(7));
1585            assert_eq!(LO_ONE, Some(3));
1586            assert_eq!(BW, 8);
1587            assert_eq!(IH.array, [0b1000_0000, 0]);
1588            assert_eq!(IL.array, [0b0000_1000, 0]);
1589            assert!(SHLEX.is_some());
1590            assert!(SHREX.is_some());
1591            assert_eq!(FSHL.array, [0x03, 0]);
1592            assert_eq!(FSHR.array, [0x00, 0xC0]);
1593            assert_eq!(DEP.array, [0b0101_0000, 0]);
1594            assert_eq!(EXT.array, [0b101, 0]);
1595        }
1596    }
1597}