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            (*self).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            (*self).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            (*self).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            (*self).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            (*self).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            (*self).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            (*self).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            (*self).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 load-bearing — 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        fn overflowing_shl(self, bits: u32) -> (Self, bool) {
425            let (shift, overflow) = normalize_shift_amount(bits, Self::BIT_SIZE);
426            let res = core::ops::Shl::<usize>::shl(self, shift);
427            (res, overflow)
428        }
429    }
430
431    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> OverflowingShr for FixedUInt<T, N, P> {
432        fn overflowing_shr(self, bits: u32) -> (Self, bool) {
433            let (shift, overflow) = normalize_shift_amount(bits, Self::BIT_SIZE);
434            let res = core::ops::Shr::<usize>::shr(self, shift);
435            (res, overflow)
436        }
437    }
438
439    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> WrappingShl for FixedUInt<T, N, P> {
440        fn wrapping_shl(self, bits: u32) -> Self {
441            OverflowingShl::overflowing_shl(self, bits).0
442        }
443    }
444
445    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> WrappingShr for FixedUInt<T, N, P> {
446        fn wrapping_shr(self, bits: u32) -> Self {
447            OverflowingShr::overflowing_shr(self, bits).0
448        }
449    }
450
451    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> CheckedShl for FixedUInt<T, N, P> {
452        fn checked_shl(self, bits: u32) -> Option<Self> {
453            let (res, overflow) = OverflowingShl::overflowing_shl(self, bits);
454            if overflow { None } else { Some(res) }
455        }
456    }
457
458    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> CheckedShr for FixedUInt<T, N, P> {
459        fn checked_shr(self, bits: u32) -> Option<Self> {
460            let (res, overflow) = OverflowingShr::overflowing_shr(self, bits);
461            if overflow { None } else { Some(res) }
462        }
463    }
464
465    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> UnboundedShl for FixedUInt<T, N, P> {
466        fn unbounded_shl(self, rhs: u32) -> Self {
467            const_unbounded_shl_u32(self, rhs)
468        }
469    }
470
471    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> UnboundedShr for FixedUInt<T, N, P> {
472        fn unbounded_shr(self, rhs: u32) -> Self {
473            const_unbounded_shr_u32(self, rhs)
474        }
475    }
476
477    // --- Reference-receiver shift impls (see add_sub_impl.rs for rationale) ---
478    //
479    // Output comes from the operator supertrait (`Shl<u32>` / `Shr<u32>`
480    // for `&FixedUInt`, defined earlier in this c0nst! block), so
481    // Output resolves to `FixedUInt<T,N,P>`.
482
483    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> OverflowingShl for &FixedUInt<T, N, P> {
484        fn overflowing_shl(self, bits: u32) -> (FixedUInt<T, N, P>, bool) {
485            <FixedUInt<T, N, P> as OverflowingShl>::overflowing_shl(*self, bits)
486        }
487    }
488
489    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> OverflowingShr for &FixedUInt<T, N, P> {
490        fn overflowing_shr(self, bits: u32) -> (FixedUInt<T, N, P>, bool) {
491            <FixedUInt<T, N, P> as OverflowingShr>::overflowing_shr(*self, bits)
492        }
493    }
494
495    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> WrappingShl for &FixedUInt<T, N, P> {
496        fn wrapping_shl(self, bits: u32) -> FixedUInt<T, N, P> {
497            <FixedUInt<T, N, P> as WrappingShl>::wrapping_shl(*self, bits)
498        }
499    }
500
501    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> WrappingShr for &FixedUInt<T, N, P> {
502        fn wrapping_shr(self, bits: u32) -> FixedUInt<T, N, P> {
503            <FixedUInt<T, N, P> as WrappingShr>::wrapping_shr(*self, bits)
504        }
505    }
506
507    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> CheckedShl for &FixedUInt<T, N, P> {
508        fn checked_shl(self, bits: u32) -> Option<FixedUInt<T, N, P>> {
509            <FixedUInt<T, N, P> as CheckedShl>::checked_shl(*self, bits)
510        }
511    }
512
513    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> CheckedShr for &FixedUInt<T, N, P> {
514        fn checked_shr(self, bits: u32) -> Option<FixedUInt<T, N, P>> {
515            <FixedUInt<T, N, P> as CheckedShr>::checked_shr(*self, bits)
516        }
517    }
518
519    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> UnboundedShl for &FixedUInt<T, N, P> {
520        fn unbounded_shl(self, rhs: u32) -> FixedUInt<T, N, P> {
521            <FixedUInt<T, N, P> as UnboundedShl>::unbounded_shl(*self, rhs)
522        }
523    }
524
525    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> UnboundedShr for &FixedUInt<T, N, P> {
526        fn unbounded_shr(self, rhs: u32) -> FixedUInt<T, N, P> {
527            <FixedUInt<T, N, P> as UnboundedShr>::unbounded_shr(*self, rhs)
528        }
529    }
530
531    // --- HighestOne / LowestOne ---------------------------------------------
532    //
533    // Indices of the highest / lowest set bit. Both reduce to the leading-
534    // and trailing-zero counts we already compute (Nct fast-path through
535    // `const_leading_zeros` / `const_trailing_zeros`; Ct path through the
536    // mask-select `_ct` variants).
537    //
538    // NOTE: NOT constant-time on a `FixedUInt<_, _, Ct>` carrier — the
539    // `Option` return shape leaks whether `self == 0` regardless of what
540    // the caller does with the value. Ct callers whose input might be
541    // zero should mask it separately (`CtIsZero::ct_is_zero`) before
542    // consulting these methods, or avoid them entirely.
543
544    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::HighestOne for FixedUInt<T, N, P> {
545        fn highest_one(self) -> Option<u32> {
546            let lz = <Self as const_num_traits::PrimBits>::leading_zeros(self);
547            if lz as usize == Self::BIT_SIZE {
548                None
549            } else {
550                Some(Self::BIT_SIZE as u32 - 1 - lz)
551            }
552        }
553    }
554
555    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::LowestOne for FixedUInt<T, N, P> {
556        fn lowest_one(self) -> Option<u32> {
557            let tz = <Self as const_num_traits::PrimBits>::trailing_zeros(self);
558            if tz as usize == Self::BIT_SIZE {
559                None
560            } else {
561                Some(tz)
562            }
563        }
564    }
565
566    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::HighestOne for &FixedUInt<T, N, P> {
567        fn highest_one(self) -> Option<u32> {
568            <FixedUInt<T, N, P> as const_num_traits::HighestOne>::highest_one(*self)
569        }
570    }
571
572    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::LowestOne for &FixedUInt<T, N, P> {
573        fn lowest_one(self) -> Option<u32> {
574            <FixedUInt<T, N, P> as const_num_traits::LowestOne>::lowest_one(*self)
575        }
576    }
577
578    // --- BitWidth ----------------------------------------------------------
579    //
580    // Minimum bits to represent self: `BIT_SIZE - leading_zeros(self)`.
581    // Returns 0 for 0. Mirrors std's `u32::BITS - n.leading_zeros()`.
582
583    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::BitWidth for FixedUInt<T, N, P> {
584        fn bit_width(self) -> u32 {
585            Self::BIT_SIZE as u32 - <Self as const_num_traits::PrimBits>::leading_zeros(self)
586        }
587    }
588
589    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::BitWidth for &FixedUInt<T, N, P> {
590        fn bit_width(self) -> u32 {
591            <FixedUInt<T, N, P> as const_num_traits::BitWidth>::bit_width(*self)
592        }
593    }
594
595    // --- IsolateHighestOne / IsolateLowestOne ------------------------------
596    //
597    // Mask the value down to just its highest / lowest set bit.
598    // IsolateHighestOne: `0` → `0`, else `1 << (BIT_SIZE - 1 - leading_zeros)`.
599    // IsolateLowestOne: the classic `self & self.wrapping_neg()` trick
600    //   (which yields 0 for 0 input automatically) — and uses arithmetic
601    //   already implemented uniformly across personalities.
602
603    // NOTE: `isolate_highest_one` is NOT constant-time for FixedUInt under
604    // Ct. The `if lz as usize == Self::BIT_SIZE` branch is value-dependent
605    // (it leaks whether `self == 0`), and the `pos`-parameterized shift's
606    // bit-count is value-dependent too. `IsolateLowestOne` below uses the
607    // `self & (0 - self)` trick and IS branchless; callers needing a
608    // constant-time highest-bit isolation on a Ct carrier should mask
609    // through a different path.
610    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::IsolateHighestOne for FixedUInt<T, N, P> {
611        type Output = Self;
612        fn isolate_highest_one(self) -> Self {
613            let lz = <Self as const_num_traits::PrimBits>::leading_zeros(self);
614            if lz as usize == Self::BIT_SIZE {
615                // self == 0; preserve the zero.
616                <Self as const_num_traits::ConstZero>::ZERO
617            } else {
618                let pos = Self::BIT_SIZE as u32 - 1 - lz;
619                <Self as const_num_traits::ConstOne>::ONE << (pos as usize)
620            }
621        }
622    }
623
624    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::IsolateLowestOne for FixedUInt<T, N, P> {
625        type Output = Self;
626        fn isolate_lowest_one(self) -> Self {
627            // `self & (-self)`. For unsigned `-x` is `wrapping_neg(x) =
628            // (0).wrapping_sub(x)`. Works for `self == 0` (0 & 0 = 0).
629            let neg = <Self as const_num_traits::WrappingSub>::wrapping_sub(
630                <Self as const_num_traits::ConstZero>::ZERO,
631                self,
632            );
633            self & neg
634        }
635    }
636
637    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::IsolateHighestOne for &FixedUInt<T, N, P> {
638        type Output = FixedUInt<T, N, P>;
639        fn isolate_highest_one(self) -> FixedUInt<T, N, P> {
640            <FixedUInt<T, N, P> as const_num_traits::IsolateHighestOne>::isolate_highest_one(*self)
641        }
642    }
643
644    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::IsolateLowestOne for &FixedUInt<T, N, P> {
645        type Output = FixedUInt<T, N, P>;
646        fn isolate_lowest_one(self) -> FixedUInt<T, N, P> {
647            <FixedUInt<T, N, P> as const_num_traits::IsolateLowestOne>::isolate_lowest_one(*self)
648        }
649    }
650
651    // --- ShlExact / ShrExact -----------------------------------------------
652    //
653    // Reversible (lossless) shifts: return `None` if any one-bit would be
654    // shifted out, or `rhs >= BIT_SIZE`. Mirrors core's primitive impls
655    // exactly (compare `rhs` against `leading_zeros` / `trailing_zeros`).
656    //
657    // NOTE: NOT constant-time on a `FixedUInt<_, _, Ct>` carrier — the
658    // `Option` return shape leaks a range predicate on
659    // `leading_zeros(self)` / `trailing_zeros(self)`, i.e. the bit-width
660    // of the secret. Ct callers wanting exact shifts should either avoid
661    // this trait or gate on their own precondition before calling.
662
663    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::ShlExact for FixedUInt<T, N, P> {
664        fn shl_exact(self, rhs: u32) -> Option<Self> {
665            if (rhs as usize) < Self::BIT_SIZE
666                && rhs <= <Self as const_num_traits::PrimBits>::leading_zeros(self)
667            {
668                Some(self << (rhs as usize))
669            } else {
670                None
671            }
672        }
673    }
674
675    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::ShrExact for FixedUInt<T, N, P> {
676        fn shr_exact(self, rhs: u32) -> Option<Self> {
677            if (rhs as usize) < Self::BIT_SIZE
678                && rhs <= <Self as const_num_traits::PrimBits>::trailing_zeros(self)
679            {
680                Some(self >> (rhs as usize))
681            } else {
682                None
683            }
684        }
685    }
686
687    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::ShlExact for &FixedUInt<T, N, P> {
688        fn shl_exact(self, rhs: u32) -> Option<FixedUInt<T, N, P>> {
689            <FixedUInt<T, N, P> as const_num_traits::ShlExact>::shl_exact(*self, rhs)
690        }
691    }
692
693    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::ShrExact for &FixedUInt<T, N, P> {
694        fn shr_exact(self, rhs: u32) -> Option<FixedUInt<T, N, P>> {
695            <FixedUInt<T, N, P> as const_num_traits::ShrExact>::shr_exact(*self, rhs)
696        }
697    }
698
699    // --- FunnelShl / FunnelShr ---------------------------------------------
700    //
701    // Double-width funnel shift: form the conceptual `(hi, lo)` value of
702    // width `2 * BIT_SIZE`, shift by `n`, and return one half. `n` is a
703    // public parameter (loop counters, fixed amounts), so the `n >= BIT_SIZE`
704    // panic is value-independent and safe for both personalities. The shift
705    // ops dispatched by `<<` / `>>` are personality-aware (Ct uses the
706    // mask-AND-XOR variant), so the funnel impl inherits that.
707
708    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::FunnelShl for FixedUInt<T, N, P> {
709        type Output = Self;
710        fn funnel_shl(self, rhs: Self, n: u32) -> Self {
711            assert!((n as usize) < Self::BIT_SIZE, "FixedUInt::funnel_shl: n out of range");
712            if n == 0 {
713                self
714            } else {
715                let lo_shift = Self::BIT_SIZE as u32 - n;
716                (self << (n as usize)) | (rhs >> (lo_shift as usize))
717            }
718        }
719    }
720
721    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::FunnelShr for FixedUInt<T, N, P> {
722        type Output = Self;
723        fn funnel_shr(self, rhs: Self, n: u32) -> Self {
724            assert!((n as usize) < Self::BIT_SIZE, "FixedUInt::funnel_shr: n out of range");
725            if n == 0 {
726                rhs
727            } else {
728                let hi_shift = Self::BIT_SIZE as u32 - n;
729                (rhs >> (n as usize)) | (self << (hi_shift as usize))
730            }
731        }
732    }
733
734    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::FunnelShl for &FixedUInt<T, N, P> {
735        type Output = FixedUInt<T, N, P>;
736        fn funnel_shl(self, rhs: Self, n: u32) -> FixedUInt<T, N, P> {
737            <FixedUInt<T, N, P> as const_num_traits::FunnelShl>::funnel_shl(*self, *rhs, n)
738        }
739    }
740
741    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> const_num_traits::FunnelShr for &FixedUInt<T, N, P> {
742        type Output = FixedUInt<T, N, P>;
743        fn funnel_shr(self, rhs: Self, n: u32) -> FixedUInt<T, N, P> {
744            <FixedUInt<T, N, P> as const_num_traits::FunnelShr>::funnel_shr(*self, *rhs, n)
745        }
746    }
747
748    // --- DepositBits / ExtractBits (PDEP / PEXT) ---------------------------
749    //
750    // Nct-only: the natural implementation iterates once per set bit of the
751    // mask, which is value-dependent. A constant-time version would have to
752    // iterate `BIT_SIZE` times unconditionally (and mask-select per step),
753    // which is a worthwhile but separate Ct-fixture exercise. For now we
754    // gate on `P = Nct`, matching how `CheckedDiv`/`CheckedRem` and the
755    // `Strict*` family are gated.
756
757    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> const_num_traits::DepositBits for FixedUInt<T, N, Nct> {
758        type Output = Self;
759        fn deposit_bits(self, mask: Self) -> Self {
760            // Scatter contiguous low bits of `self` into positions of the
761            // one-bits of `mask`. Iterates once per set bit of `mask`.
762            let mut result = <Self as const_num_traits::ConstZero>::ZERO;
763            let mut remaining = mask;
764            let mut bb = <Self as const_num_traits::ConstOne>::ONE;
765            while !<Self as const_num_traits::Zero>::is_zero(&remaining) {
766                // Lowest set bit of `remaining` via `x & -x`.
767                let lowest = <Self as const_num_traits::IsolateLowestOne>::isolate_lowest_one(remaining);
768                if !<Self as const_num_traits::Zero>::is_zero(&(self & bb)) {
769                    result |= lowest;
770                }
771                remaining = remaining & <Self as const_num_traits::WrappingSub>::wrapping_sub(
772                    remaining,
773                    <Self as const_num_traits::ConstOne>::ONE,
774                );
775                bb = <Self as const_num_traits::WrappingShl>::wrapping_shl(bb, 1);
776            }
777            result
778        }
779    }
780
781    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> const_num_traits::ExtractBits for FixedUInt<T, N, Nct> {
782        type Output = Self;
783        fn extract_bits(self, mask: Self) -> Self {
784            // Gather the bits of `self` selected by `mask` into the low end
785            // of the result. Mirror of `deposit_bits`.
786            let mut result = <Self as const_num_traits::ConstZero>::ZERO;
787            let mut remaining = mask;
788            let mut bb = <Self as const_num_traits::ConstOne>::ONE;
789            while !<Self as const_num_traits::Zero>::is_zero(&remaining) {
790                let lowest = <Self as const_num_traits::IsolateLowestOne>::isolate_lowest_one(remaining);
791                if !<Self as const_num_traits::Zero>::is_zero(&(self & lowest)) {
792                    result |= bb;
793                }
794                remaining = remaining & <Self as const_num_traits::WrappingSub>::wrapping_sub(
795                    remaining,
796                    <Self as const_num_traits::ConstOne>::ONE,
797                );
798                bb = <Self as const_num_traits::WrappingShl>::wrapping_shl(bb, 1);
799            }
800            result
801        }
802    }
803
804    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> const_num_traits::DepositBits for &FixedUInt<T, N, Nct> {
805        type Output = FixedUInt<T, N, Nct>;
806        fn deposit_bits(self, mask: Self) -> FixedUInt<T, N, Nct> {
807            <FixedUInt<T, N, Nct> as const_num_traits::DepositBits>::deposit_bits(*self, *mask)
808        }
809    }
810
811    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize> const_num_traits::ExtractBits for &FixedUInt<T, N, Nct> {
812        type Output = FixedUInt<T, N, Nct>;
813        fn extract_bits(self, mask: Self) -> FixedUInt<T, N, Nct> {
814            <FixedUInt<T, N, Nct> as const_num_traits::ExtractBits>::extract_bits(*self, *mask)
815        }
816    }
817}
818
819// num_traits wrappers - delegate to const impls
820#[cfg(feature = "num-traits")]
821impl<T: MachineWord, const N: usize, P: Personality> num_traits::WrappingShl
822    for FixedUInt<T, N, P>
823{
824    fn wrapping_shl(&self, bits: u32) -> Self {
825        WrappingShl::wrapping_shl(*self, bits)
826    }
827}
828
829#[cfg(feature = "num-traits")]
830impl<T: MachineWord, const N: usize, P: Personality> num_traits::WrappingShr
831    for FixedUInt<T, N, P>
832{
833    fn wrapping_shr(&self, bits: u32) -> Self {
834        WrappingShr::wrapping_shr(*self, bits)
835    }
836}
837
838#[cfg(feature = "num-traits")]
839impl<T: MachineWord, const N: usize, P: Personality> num_traits::CheckedShl for FixedUInt<T, N, P> {
840    fn checked_shl(&self, bits: u32) -> Option<Self> {
841        CheckedShl::checked_shl(*self, bits)
842    }
843}
844
845#[cfg(feature = "num-traits")]
846impl<T: MachineWord, const N: usize, P: Personality> num_traits::CheckedShr for FixedUInt<T, N, P> {
847    fn checked_shr(&self, bits: u32) -> Option<Self> {
848        CheckedShr::checked_shr(*self, bits)
849    }
850}
851
852#[cfg(test)]
853// Coverage tests deliberately exercise every ref/value combination of
854// the bitwise/shift operators (see `test_*_combinations`).
855#[allow(clippy::op_ref)]
856mod tests {
857    use super::*;
858
859    #[test]
860    fn test_bitand_combinations() {
861        let a = FixedUInt::<u8, 2>::from(12u8); // 1100
862        let b = FixedUInt::<u8, 2>::from(10u8); // 1010
863        let expected = FixedUInt::<u8, 2>::from(8u8); // 1000
864
865        // value & value
866        assert_eq!(a & b, expected);
867        // value & ref
868        assert_eq!(a & &b, expected);
869        // ref & value
870        assert_eq!(&a & b, expected);
871        // ref & ref
872        assert_eq!(&a & &b, expected);
873    }
874
875    #[test]
876    fn test_bitor_combinations() {
877        let a = FixedUInt::<u8, 2>::from(12u8); // 1100
878        let b = FixedUInt::<u8, 2>::from(10u8); // 1010
879        let expected = FixedUInt::<u8, 2>::from(14u8); // 1110
880
881        // value | value
882        assert_eq!(a | b, expected);
883        // value | ref
884        assert_eq!(a | &b, expected);
885        // ref | value
886        assert_eq!(&a | b, expected);
887        // ref | ref
888        assert_eq!(&a | &b, expected);
889    }
890
891    #[test]
892    fn test_bitxor_combinations() {
893        let a = FixedUInt::<u8, 2>::from(12u8); // 1100
894        let b = FixedUInt::<u8, 2>::from(10u8); // 1010
895        let expected = FixedUInt::<u8, 2>::from(6u8); // 0110
896
897        // value ^ value
898        assert_eq!(a ^ b, expected);
899        // value ^ ref
900        assert_eq!(a ^ &b, expected);
901        // ref ^ value
902        assert_eq!(&a ^ b, expected);
903        // ref ^ ref
904        assert_eq!(&a ^ &b, expected);
905    }
906
907    #[test]
908    fn test_shl_combinations() {
909        let a = FixedUInt::<u8, 2>::from(2u8); // 0010
910        let shift: usize = 2;
911        let expected = FixedUInt::<u8, 2>::from(8u8); // 1000
912
913        // value << value
914        assert_eq!(a << shift, expected);
915        // value << ref
916        assert_eq!(a << &shift, expected);
917        // ref << value
918        assert_eq!(&a << shift, expected);
919        // ref << ref
920        assert_eq!(&a << &shift, expected);
921
922        // Same with u32
923        let shift32: u32 = 2;
924        assert_eq!(a << shift32, expected);
925        assert_eq!(a << &shift32, expected);
926        assert_eq!(&a << shift32, expected);
927        assert_eq!(&a << &shift32, expected);
928    }
929
930    #[test]
931    fn test_shr_combinations() {
932        let a = FixedUInt::<u8, 2>::from(8u8); // 1000
933        let shift: usize = 2;
934        let expected = FixedUInt::<u8, 2>::from(2u8); // 0010
935
936        // value >> value
937        assert_eq!(a >> shift, expected);
938        // value >> ref
939        assert_eq!(a >> &shift, expected);
940        // ref >> value
941        assert_eq!(&a >> shift, expected);
942        // ref >> ref
943        assert_eq!(&a >> &shift, expected);
944
945        // Same with u32
946        let shift32: u32 = 2;
947        assert_eq!(a >> shift32, expected);
948        assert_eq!(a >> &shift32, expected);
949        assert_eq!(&a >> shift32, expected);
950        assert_eq!(&a >> &shift32, expected);
951    }
952
953    #[test]
954    fn test_const_bitops() {
955        type TestInt = FixedUInt<u8, 2>;
956
957        let a = TestInt::from(0b11001100u8);
958        let b = TestInt::from(0b10101010u8);
959
960        // Test not
961        let not_a = !a;
962        assert_eq!(not_a.array[0], 0b00110011);
963        assert_eq!(not_a.array[1], 0xFF);
964
965        // Test bitand
966        assert_eq!(a & b, TestInt::from(0b10001000u8));
967
968        // Test bitor
969        assert_eq!(a | b, TestInt::from(0b11101110u8));
970
971        // Test bitxor
972        assert_eq!(a ^ b, TestInt::from(0b01100110u8));
973
974        // Test shl
975        assert_eq!(TestInt::from(1u8) << 4usize, TestInt::from(16u8));
976
977        // Test shr
978        assert_eq!(TestInt::from(16u8) >> 2usize, TestInt::from(4u8));
979
980        #[cfg(feature = "nightly")]
981        {
982            const A: TestInt = FixedUInt::from_array([0b11001100, 0]);
983            const B: TestInt = FixedUInt::from_array([0b10101010, 0]);
984
985            const NOT_A: TestInt = !A;
986            const AND_AB: TestInt = A & B;
987            const OR_AB: TestInt = A | B;
988            const XOR_AB: TestInt = A ^ B;
989            const SHL_1: TestInt = FixedUInt::from_array([1u8, 0]) << 4usize;
990            const SHR_16: TestInt = FixedUInt::from_array([16u8, 0]) >> 2usize;
991
992            assert_eq!(NOT_A.array[0], 0b00110011);
993            assert_eq!(AND_AB.array[0], 0b10001000);
994            assert_eq!(OR_AB.array[0], 0b11101110);
995            assert_eq!(XOR_AB.array[0], 0b01100110);
996            assert_eq!(SHL_1.array[0], 16);
997            assert_eq!(SHR_16.array[0], 4);
998        }
999    }
1000
1001    #[test]
1002    fn test_const_shift_traits() {
1003        type TestInt = FixedUInt<u8, 2>; // 16-bit
1004
1005        // Test overflowing_shl
1006        let a = TestInt::from(0x80u8); // 0x0080
1007        let (res, overflow) = OverflowingShl::overflowing_shl(a, 8);
1008        assert_eq!(res.array, [0, 0x80]); // 0x8000
1009        assert!(!overflow);
1010
1011        let (res, overflow) = OverflowingShl::overflowing_shl(a, 16);
1012        assert_eq!(res.array, [0x80, 0]); // wraps around
1013        assert!(overflow);
1014
1015        let (res, overflow) = OverflowingShl::overflowing_shl(a, 9);
1016        assert_eq!(res.array, [0, 0]); // high bits shifted out (but shift < bit_width)
1017        assert!(!overflow); // 9 < 16, so no overflow
1018
1019        // Test overflowing_shr
1020        let b = TestInt::from(0x0100u16); // 0x0100
1021        let (res, overflow) = OverflowingShr::overflowing_shr(b, 8);
1022        assert_eq!(res.array, [1, 0]); // 0x0001
1023        assert!(!overflow);
1024
1025        let (res, overflow) = OverflowingShr::overflowing_shr(b, 16);
1026        assert_eq!(res.array, [0, 1]); // wraps
1027        assert!(overflow);
1028
1029        // Test wrapping_shl
1030        let c = TestInt::from(1u8);
1031        assert_eq!(WrappingShl::wrapping_shl(c, 4).array, [16, 0]);
1032        assert_eq!(WrappingShl::wrapping_shl(c, 16).array, [1, 0]); // wraps
1033        assert_eq!(WrappingShl::wrapping_shl(c, 17).array, [2, 0]); // wraps
1034
1035        // Test wrapping_shr
1036        let d = TestInt::from(0x8000u16);
1037        assert_eq!(WrappingShr::wrapping_shr(d, 4).array, [0, 0x08]);
1038        assert_eq!(WrappingShr::wrapping_shr(d, 16).array, [0, 0x80]); // wraps
1039        assert_eq!(WrappingShr::wrapping_shr(d, 17).array, [0, 0x40]); // wraps
1040
1041        // Test checked_shl
1042        let e = TestInt::from(1u8);
1043        assert_eq!(CheckedShl::checked_shl(e, 4), Some(TestInt::from(16u8)));
1044        assert_eq!(
1045            CheckedShl::checked_shl(e, 15),
1046            Some(TestInt::from(0x8000u16))
1047        );
1048        assert_eq!(CheckedShl::checked_shl(e, 16), None); // overflow
1049
1050        // Test checked_shr
1051        let f = TestInt::from(0x8000u16);
1052        assert_eq!(CheckedShr::checked_shr(f, 15), Some(TestInt::from(1u8)));
1053        assert_eq!(CheckedShr::checked_shr(f, 16), None); // overflow
1054
1055        // Test edge case: zero shift
1056        let g = TestInt::from(42u8);
1057        assert_eq!(OverflowingShl::overflowing_shl(g, 0), (g, false));
1058        assert_eq!(OverflowingShr::overflowing_shr(g, 0), (g, false));
1059        assert_eq!(WrappingShl::wrapping_shl(g, 0), g);
1060        assert_eq!(WrappingShr::wrapping_shr(g, 0), g);
1061        assert_eq!(CheckedShl::checked_shl(g, 0), Some(g));
1062        assert_eq!(CheckedShr::checked_shr(g, 0), Some(g));
1063    }
1064
1065    #[test]
1066    fn test_const_shift_traits_n0() {
1067        // Test with N=0 (zero-sized type)
1068        type ZeroInt = FixedUInt<u8, 0>;
1069        let z = ZeroInt::from_array([]);
1070
1071        // All shifts on zero-sized type should overflow
1072        assert_eq!(OverflowingShl::overflowing_shl(z, 0), (z, true));
1073        assert_eq!(OverflowingShr::overflowing_shr(z, 0), (z, true));
1074        assert_eq!(WrappingShl::wrapping_shl(z, 0), z);
1075        assert_eq!(WrappingShr::wrapping_shr(z, 0), z);
1076        assert_eq!(CheckedShl::checked_shl(z, 0), None);
1077        assert_eq!(CheckedShr::checked_shr(z, 0), None);
1078    }
1079
1080    #[test]
1081    #[cfg(feature = "num-traits")]
1082    fn test_num_traits_shift_wrappers() {
1083        use num_traits::{CheckedShl, CheckedShr, WrappingShl, WrappingShr};
1084
1085        type TestInt = FixedUInt<u8, 2>;
1086
1087        let a = TestInt::from(1u8);
1088
1089        // num_traits::WrappingShl is by-ref (upstream signature).
1090        assert_eq!(WrappingShl::wrapping_shl(&a, 4), TestInt::from(16u8));
1091        assert_eq!(WrappingShl::wrapping_shl(&a, 16), a); // wraps
1092
1093        // num_traits::WrappingShr
1094        let b = TestInt::from(16u8);
1095        assert_eq!(WrappingShr::wrapping_shr(&b, 4), TestInt::from(1u8));
1096
1097        // num_traits::CheckedShl
1098        assert_eq!(CheckedShl::checked_shl(&a, 4), Some(TestInt::from(16u8)));
1099        assert_eq!(CheckedShl::checked_shl(&a, 16), None);
1100
1101        // num_traits::CheckedShr
1102        assert_eq!(CheckedShr::checked_shr(&b, 4), Some(TestInt::from(1u8)));
1103        assert_eq!(CheckedShr::checked_shr(&b, 16), None);
1104    }
1105
1106    #[test]
1107    fn test_unbounded_shift() {
1108        type U16 = FixedUInt<u8, 2>;
1109
1110        let one = U16::from(1u8);
1111
1112        // Normal shifts (within bounds)
1113        assert_eq!(UnboundedShl::unbounded_shl(one, 0), one);
1114        assert_eq!(UnboundedShl::unbounded_shl(one, 4), U16::from(16u8));
1115        assert_eq!(UnboundedShl::unbounded_shl(one, 15), U16::from(0x8000u16));
1116
1117        assert_eq!(UnboundedShr::unbounded_shr(U16::from(0x8000u16), 15), one);
1118        assert_eq!(UnboundedShr::unbounded_shr(U16::from(16u8), 4), one);
1119
1120        // At boundary (shift by bit width) - returns 0
1121        assert_eq!(UnboundedShl::unbounded_shl(one, 16), U16::from(0u8));
1122        assert_eq!(
1123            UnboundedShr::unbounded_shr(U16::from(0xFFFFu16), 16),
1124            U16::from(0u8)
1125        );
1126
1127        // Beyond boundary - returns 0
1128        assert_eq!(
1129            UnboundedShl::unbounded_shl(U16::from(0xFFFFu16), 17),
1130            U16::from(0u8)
1131        );
1132        assert_eq!(
1133            UnboundedShl::unbounded_shl(U16::from(0xFFFFu16), 100),
1134            U16::from(0u8)
1135        );
1136        assert_eq!(
1137            UnboundedShr::unbounded_shr(U16::from(0xFFFFu16), 17),
1138            U16::from(0u8)
1139        );
1140        assert_eq!(
1141            UnboundedShr::unbounded_shr(U16::from(0xFFFFu16), 100),
1142            U16::from(0u8)
1143        );
1144
1145        // Test with different word sizes
1146        type U32 = FixedUInt<u8, 4>;
1147        let one32 = U32::from(1u8);
1148        assert_eq!(
1149            UnboundedShl::unbounded_shl(one32, 31),
1150            U32::from(0x80000000u32)
1151        );
1152        assert_eq!(UnboundedShl::unbounded_shl(one32, 32), U32::from(0u8));
1153        assert_eq!(
1154            UnboundedShr::unbounded_shr(U32::from(0x80000000u32), 31),
1155            one32
1156        );
1157        assert_eq!(
1158            UnboundedShr::unbounded_shr(U32::from(0x80000000u32), 32),
1159            U32::from(0u8)
1160        );
1161    }
1162
1163    #[test]
1164    fn test_unbounded_shift_polymorphic() {
1165        fn test_unbounded<T>(val: T, shift: u32, expected_shl: T, expected_shr: T)
1166        where
1167            T: UnboundedShl<Output = T>
1168                + UnboundedShr<Output = T>
1169                + core::ops::Shl<u32, Output = T>
1170                + core::ops::Shr<u32, Output = T>
1171                + Eq
1172                + core::fmt::Debug
1173                + Copy,
1174        {
1175            assert_eq!(UnboundedShl::unbounded_shl(val, shift), expected_shl);
1176            assert_eq!(UnboundedShr::unbounded_shr(val, shift), expected_shr);
1177        }
1178
1179        // Test with FixedUInt layouts
1180        type U8x2 = FixedUInt<u8, 2>;
1181        type U8x4 = FixedUInt<u8, 4>;
1182        type U16x2 = FixedUInt<u16, 2>;
1183
1184        // Same logical shift, different layouts
1185        test_unbounded(U8x2::from(1u8), 4, U8x2::from(16u8), U8x2::from(0u8));
1186        test_unbounded(U8x4::from(1u8), 4, U8x4::from(16u8), U8x4::from(0u8));
1187        test_unbounded(U16x2::from(1u8), 4, U16x2::from(16u8), U16x2::from(0u8));
1188
1189        // Test with primitives
1190        test_unbounded(1u8, 4, 16u8, 0u8);
1191        test_unbounded(1u16, 4, 16u16, 0u16);
1192        test_unbounded(1u32, 4, 16u32, 0u32);
1193
1194        // Boundary tests
1195        test_unbounded(1u8, 8, 0u8, 0u8);
1196        test_unbounded(U8x2::from(1u8), 16, U8x2::from(0u8), U8x2::from(0u8));
1197    }
1198
1199    #[test]
1200    fn test_bit_width() {
1201        use const_num_traits::BitWidth;
1202        type U16 = FixedUInt<u8, 2>;
1203        assert_eq!(BitWidth::bit_width(U16::from(0u8)), 0);
1204        assert_eq!(BitWidth::bit_width(U16::from(1u8)), 1);
1205        assert_eq!(BitWidth::bit_width(U16::from(2u8)), 2);
1206        assert_eq!(BitWidth::bit_width(U16::from(3u8)), 2);
1207        assert_eq!(BitWidth::bit_width(U16::from(255u8)), 8);
1208        assert_eq!(BitWidth::bit_width(U16::from(256u16)), 9);
1209        assert_eq!(BitWidth::bit_width(U16::from(0xFFFFu16)), 16);
1210    }
1211
1212    #[test]
1213    fn test_highest_lowest_one() {
1214        use const_num_traits::{HighestOne, LowestOne};
1215        type U16 = FixedUInt<u8, 2>;
1216        assert_eq!(HighestOne::highest_one(U16::from(0u8)), None);
1217        assert_eq!(HighestOne::highest_one(U16::from(1u8)), Some(0));
1218        assert_eq!(HighestOne::highest_one(U16::from(0b1010_0000u8)), Some(7));
1219        assert_eq!(HighestOne::highest_one(U16::from(0x8000u16)), Some(15));
1220
1221        assert_eq!(LowestOne::lowest_one(U16::from(0u8)), None);
1222        assert_eq!(LowestOne::lowest_one(U16::from(1u8)), Some(0));
1223        assert_eq!(LowestOne::lowest_one(U16::from(0b0010_1000u8)), Some(3));
1224        assert_eq!(LowestOne::lowest_one(U16::from(0x8000u16)), Some(15));
1225    }
1226
1227    #[test]
1228    fn test_isolate_highest_lowest_one() {
1229        use const_num_traits::{IsolateHighestOne, IsolateLowestOne};
1230        type U16 = FixedUInt<u8, 2>;
1231        // zero → zero
1232        assert_eq!(
1233            IsolateHighestOne::isolate_highest_one(U16::from(0u8)),
1234            U16::from(0u8)
1235        );
1236        assert_eq!(
1237            IsolateLowestOne::isolate_lowest_one(U16::from(0u8)),
1238            U16::from(0u8)
1239        );
1240        // nonzero
1241        assert_eq!(
1242            IsolateHighestOne::isolate_highest_one(U16::from(0b1010_0000u8)),
1243            U16::from(0b1000_0000u8)
1244        );
1245        assert_eq!(
1246            IsolateLowestOne::isolate_lowest_one(U16::from(0b1010_1000u8)),
1247            U16::from(0b0000_1000u8)
1248        );
1249        // power of two: highest == lowest == self
1250        let p: U16 = U16::from(0x0100u16);
1251        assert_eq!(IsolateHighestOne::isolate_highest_one(p), p);
1252        assert_eq!(IsolateLowestOne::isolate_lowest_one(p), p);
1253    }
1254
1255    #[test]
1256    fn test_shl_shr_exact() {
1257        use const_num_traits::{ShlExact, ShrExact};
1258        type U16 = FixedUInt<u8, 2>;
1259        // shl_exact: must not lose bits
1260        assert_eq!(
1261            ShlExact::shl_exact(U16::from(1u8), 4),
1262            Some(U16::from(16u8))
1263        );
1264        assert_eq!(ShlExact::shl_exact(U16::from(0u8), 8), Some(U16::from(0u8)));
1265        // dropping a high bit → None
1266        assert_eq!(ShlExact::shl_exact(U16::from(0x8000u16), 1), None);
1267        // rhs >= BIT_SIZE → None
1268        assert_eq!(ShlExact::shl_exact(U16::from(1u8), 16), None);
1269
1270        // shr_exact: must not lose set bits
1271        assert_eq!(
1272            ShrExact::shr_exact(U16::from(16u8), 4),
1273            Some(U16::from(1u8))
1274        );
1275        assert_eq!(ShrExact::shr_exact(U16::from(0u8), 8), Some(U16::from(0u8)));
1276        // dropping a low bit → None
1277        assert_eq!(ShrExact::shr_exact(U16::from(0b0001u8), 1), None);
1278        assert_eq!(ShrExact::shr_exact(U16::from(0b0011u8), 1), None);
1279        // rhs >= BIT_SIZE → None
1280        assert_eq!(ShrExact::shr_exact(U16::from(1u8), 16), None);
1281    }
1282
1283    #[test]
1284    #[allow(clippy::needless_borrows_for_generic_args)]
1285    fn test_ref_receivers_compile_through() {
1286        use const_num_traits::{BitWidth, IsolateHighestOne, IsolateLowestOne, ShlExact, ShrExact};
1287        type U16 = FixedUInt<u8, 2>;
1288        let v = U16::from(0b0010_1000u8);
1289        assert_eq!(BitWidth::bit_width(&v), 6);
1290        assert_eq!(
1291            IsolateHighestOne::isolate_highest_one(&v),
1292            U16::from(0b0010_0000u8)
1293        );
1294        assert_eq!(
1295            IsolateLowestOne::isolate_lowest_one(&v),
1296            U16::from(0b0000_1000u8)
1297        );
1298        assert_eq!(ShlExact::shl_exact(&v, 2), Some(U16::from(0b1010_0000u8)));
1299        assert_eq!(ShrExact::shr_exact(&v, 3), Some(U16::from(0b0000_0101u8)));
1300    }
1301
1302    #[test]
1303    fn test_funnel_shifts() {
1304        use const_num_traits::{FunnelShl, FunnelShr};
1305        type U16 = FixedUInt<u8, 2>;
1306
1307        // 0x0001_8000 << 1 = 0x0003_0000; high half = 0x0003.
1308        assert_eq!(
1309            FunnelShl::funnel_shl(U16::from(0x0001u16), U16::from(0x8000u16), 1),
1310            U16::from(0x0003u16),
1311        );
1312        // n == 0: returns self (hi)
1313        assert_eq!(
1314            FunnelShl::funnel_shl(U16::from(0xABCDu16), U16::from(0xFFFFu16), 0),
1315            U16::from(0xABCDu16),
1316        );
1317        // 0x0001_8000 >> 1 = 0x0000_C000; low half = 0xC000.
1318        assert_eq!(
1319            FunnelShr::funnel_shr(U16::from(0x0001u16), U16::from(0x8000u16), 1),
1320            U16::from(0xC000u16),
1321        );
1322        // n == 0: returns rhs (lo)
1323        assert_eq!(
1324            FunnelShr::funnel_shr(U16::from(0xABCDu16), U16::from(0x1234u16), 0),
1325            U16::from(0x1234u16),
1326        );
1327
1328        // Reference receivers
1329        let hi = U16::from(0x0001u16);
1330        let lo = U16::from(0x8000u16);
1331        assert_eq!(FunnelShl::funnel_shl(&hi, &lo, 1), U16::from(0x0003u16));
1332        assert_eq!(FunnelShr::funnel_shr(&hi, &lo, 1), U16::from(0xC000u16));
1333    }
1334
1335    #[test]
1336    #[should_panic(expected = "funnel_shl: n out of range")]
1337    fn test_funnel_shl_panics_at_bit_size() {
1338        use const_num_traits::FunnelShl;
1339        type U16 = FixedUInt<u8, 2>;
1340        let _ = FunnelShl::funnel_shl(U16::from(1u8), U16::from(0u8), 16);
1341    }
1342
1343    #[test]
1344    fn test_deposit_extract_bits() {
1345        use const_num_traits::Nct;
1346        use const_num_traits::{DepositBits, ExtractBits};
1347        type U16 = FixedUInt<u8, 2, Nct>;
1348
1349        // Mirror of the primitive doctest:
1350        // deposit_bits(0b101, mask=0b1111_0000) = 0b0101_0000
1351        assert_eq!(
1352            DepositBits::deposit_bits(U16::from(0b101u8), U16::from(0b1111_0000u8)),
1353            U16::from(0b0101_0000u8),
1354        );
1355        // extract_bits(0b0101_0011, mask=0b1111_0000) = 0b101
1356        assert_eq!(
1357            ExtractBits::extract_bits(U16::from(0b0101_0011u8), U16::from(0b1111_0000u8)),
1358            U16::from(0b101u8),
1359        );
1360
1361        // Empty mask → 0 (both directions).
1362        assert_eq!(
1363            DepositBits::deposit_bits(U16::from(0xFFFFu16), U16::from(0u8)),
1364            U16::from(0u8),
1365        );
1366        assert_eq!(
1367            ExtractBits::extract_bits(U16::from(0xFFFFu16), U16::from(0u8)),
1368            U16::from(0u8),
1369        );
1370
1371        // All-ones mask → identity.
1372        assert_eq!(
1373            DepositBits::deposit_bits(U16::from(0xABCDu16), U16::from(0xFFFFu16)),
1374            U16::from(0xABCDu16),
1375        );
1376        assert_eq!(
1377            ExtractBits::extract_bits(U16::from(0xABCDu16), U16::from(0xFFFFu16)),
1378            U16::from(0xABCDu16),
1379        );
1380
1381        // Round-trip on a non-trivial mask. extract then deposit through the
1382        // same mask gives back the originally-selected bits in their original
1383        // positions.
1384        let mask = U16::from(0b1010_1010u8);
1385        let v = U16::from(0b1111_1111u8);
1386        let extracted = ExtractBits::extract_bits(v, mask);
1387        let redeposited = DepositBits::deposit_bits(extracted, mask);
1388        assert_eq!(redeposited, v & mask);
1389
1390        // Reference receivers
1391        let v_ref = U16::from(0b0110_0110u8);
1392        let m_ref = U16::from(0b1111_0000u8);
1393        assert_eq!(
1394            ExtractBits::extract_bits(&v_ref, &m_ref),
1395            U16::from(0b0110u8),
1396        );
1397        assert_eq!(
1398            DepositBits::deposit_bits(&U16::from(0b101u8), &m_ref),
1399            U16::from(0b0101_0000u8),
1400        );
1401    }
1402
1403    // --- Empirical const-evaluability proofs ---------------------------------
1404    //
1405    // Each trait method below is invoked from a `c0nst fn` wrapper, so the
1406    // surrounding `c0nst::c0nst!` block forces the compiler to treat it as
1407    // const-callable when the `nightly` feature is enabled. The
1408    // `nightly_const_eval_*` tests then bind the wrapper's result to a
1409    // `const` item — proving the trait method actually evaluates at compile
1410    // time, not just that the impl is annotated `c0nst`.
1411
1412    c0nst::c0nst! {
1413        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) {
1414            OverflowingShl::overflowing_shl(v, bits)
1415        }
1416        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) {
1417            OverflowingShr::overflowing_shr(v, bits)
1418        }
1419        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> {
1420            WrappingShl::wrapping_shl(v, bits)
1421        }
1422        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> {
1423            WrappingShr::wrapping_shr(v, bits)
1424        }
1425        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>> {
1426            CheckedShl::checked_shl(v, bits)
1427        }
1428        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>> {
1429            CheckedShr::checked_shr(v, bits)
1430        }
1431        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> {
1432            UnboundedShl::unbounded_shl(v, bits)
1433        }
1434        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> {
1435            UnboundedShr::unbounded_shr(v, bits)
1436        }
1437        pub c0nst fn const_highest_one<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>) -> Option<u32> {
1438            const_num_traits::HighestOne::highest_one(v)
1439        }
1440        pub c0nst fn const_lowest_one<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>) -> Option<u32> {
1441            const_num_traits::LowestOne::lowest_one(v)
1442        }
1443        pub c0nst fn const_bit_width<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality>(v: FixedUInt<T, N, P>) -> u32 {
1444            const_num_traits::BitWidth::bit_width(v)
1445        }
1446        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> {
1447            const_num_traits::IsolateHighestOne::isolate_highest_one(v)
1448        }
1449        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> {
1450            const_num_traits::IsolateLowestOne::isolate_lowest_one(v)
1451        }
1452        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>> {
1453            const_num_traits::ShlExact::shl_exact(v, bits)
1454        }
1455        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>> {
1456            const_num_traits::ShrExact::shr_exact(v, bits)
1457        }
1458        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> {
1459            const_num_traits::FunnelShl::funnel_shl(hi, lo, n)
1460        }
1461        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> {
1462            const_num_traits::FunnelShr::funnel_shr(hi, lo, n)
1463        }
1464        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> {
1465            const_num_traits::DepositBits::deposit_bits(v, mask)
1466        }
1467        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> {
1468            const_num_traits::ExtractBits::extract_bits(v, mask)
1469        }
1470    }
1471
1472    #[test]
1473    fn nightly_const_eval_bit_traits() {
1474        type U16 = FixedUInt<u8, 2>;
1475
1476        // Runtime smoke — the wrappers themselves work.
1477        let v = U16::from(1u8);
1478        assert_eq!(const_overflowing_shl(v, 4), (U16::from(16u8), false));
1479        assert_eq!(const_wrapping_shl(v, 4), U16::from(16u8));
1480        assert_eq!(const_checked_shl(v, 16), None);
1481        assert_eq!(const_bit_width(U16::from(0xFFu8)), 8);
1482
1483        // The real proof — evaluate at compile time.
1484        #[cfg(feature = "nightly")]
1485        {
1486            const V: U16 = FixedUInt::from_array([1, 0]);
1487            const V_FF: U16 = FixedUInt::from_array([0xFF, 0]);
1488            const V_MASK: U16 = FixedUInt::from_array([0b1010_1000, 0]);
1489            const HI: U16 = FixedUInt::from_array([1, 0]);
1490            const LO: U16 = FixedUInt::from_array([0, 0x80]);
1491
1492            const OSHL: (U16, bool) = const_overflowing_shl(V, 4);
1493            const OSHR: (U16, bool) = const_overflowing_shr(V_FF, 4);
1494            const WSHL: U16 = const_wrapping_shl(V, 4);
1495            const WSHR: U16 = const_wrapping_shr(V_FF, 4);
1496            const CSHL: Option<U16> = const_checked_shl(V, 16);
1497            const CSHR: Option<U16> = const_checked_shr(V, 4);
1498            const USHL: U16 = const_unbounded_shl(V, 8);
1499            const USHR: U16 = const_unbounded_shr(V_FF, 4);
1500            const HI_ONE: Option<u32> = const_highest_one(V_FF);
1501            const LO_ONE: Option<u32> = const_lowest_one(V_MASK);
1502            const BW: u32 = const_bit_width(V_FF);
1503            const IH: U16 = const_isolate_highest_one(V_MASK);
1504            const IL: U16 = const_isolate_lowest_one(V_MASK);
1505            const SHLEX: Option<U16> = const_shl_exact(V, 4);
1506            const SHREX: Option<U16> = const_shr_exact(FixedUInt::from_array([16, 0]), 4);
1507            const FSHL: U16 = const_funnel_shl(HI, LO, 1);
1508            const FSHR: U16 = const_funnel_shr(HI, LO, 1);
1509            const DEP: U16 = const_deposit_bits(
1510                FixedUInt::from_array([0b101, 0]),
1511                FixedUInt::from_array([0b1111_0000, 0]),
1512            );
1513            const EXT: U16 = const_extract_bits(
1514                FixedUInt::from_array([0b0101_0011, 0]),
1515                FixedUInt::from_array([0b1111_0000, 0]),
1516            );
1517
1518            // Sanity-check a representative subset of the const results.
1519            assert_eq!(OSHL.0.array, [16, 0]);
1520            assert!(!OSHL.1);
1521            assert_eq!(OSHR.0.array, [0x0F, 0]);
1522            assert!(!OSHR.1);
1523            assert_eq!(WSHL.array, [16, 0]);
1524            assert_eq!(WSHR.array, [0x0F, 0]);
1525            assert!(CSHL.is_none());
1526            assert!(CSHR.is_some());
1527            assert_eq!(USHL.array, [0, 1]);
1528            assert_eq!(USHR.array, [0x0F, 0]);
1529            assert_eq!(HI_ONE, Some(7));
1530            assert_eq!(LO_ONE, Some(3));
1531            assert_eq!(BW, 8);
1532            assert_eq!(IH.array, [0b1000_0000, 0]);
1533            assert_eq!(IL.array, [0b0000_1000, 0]);
1534            assert!(SHLEX.is_some());
1535            assert!(SHREX.is_some());
1536            assert_eq!(FSHL.array, [0x03, 0]);
1537            assert_eq!(FSHR.array, [0x00, 0xC0]);
1538            assert_eq!(DEP.array, [0b0101_0000, 0]);
1539            assert_eq!(EXT.array, [0b101, 0]);
1540        }
1541    }
1542}