Skip to main content

fixed_bigint/fixeduint/
bit_ops_impl.rs

1use super::{const_shl_ct, const_shl_impl, const_shr_ct, const_shr_impl, FixedUInt, MachineWord};
2
3use crate::const_numtraits::{
4    ConstCheckedShl, ConstCheckedShr, ConstOverflowingShl, ConstOverflowingShr,
5    ConstUnboundedShift, ConstWrappingShl, ConstWrappingShr, ConstZero,
6};
7use crate::machineword::ConstMachineWord;
8use crate::patch_num_traits::{OverflowingShl, OverflowingShr};
9use crate::personality::{Personality, PersonalityTag};
10
11c0nst::c0nst! {
12    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Not for FixedUInt<T, N, P> {
13        type Output = Self;
14        fn not(self) -> Self::Output {
15            let mut ret = <Self as ConstZero>::zero();
16            let mut i = 0;
17            while i < N {
18                ret.array[i] = !self.array[i];
19                i += 1;
20            }
21            ret
22        }
23    }
24
25    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitAnd<&FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
26        type Output = FixedUInt<T, N, P>;
27        fn bitand(self, other: &FixedUInt<T, N, P>) -> Self::Output {
28            let mut ret = <FixedUInt<T, N, P> as ConstZero>::zero();
29            let mut i = 0;
30            while i < N {
31                ret.array[i] = self.array[i] & other.array[i];
32                i += 1;
33            }
34            ret
35        }
36    }
37
38    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitAnd for FixedUInt<T, N, P> {
39        type Output = Self;
40        fn bitand(self, other: Self) -> Self::Output {
41            (&self).bitand(&other)
42        }
43    }
44
45    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitAnd<&FixedUInt<T, N, P>> for FixedUInt<T, N, P> {
46        type Output = Self;
47        fn bitand(self, other: &FixedUInt<T, N, P>) -> Self::Output {
48            (&self).bitand(other)
49        }
50    }
51
52    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitAnd<FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
53        type Output = FixedUInt<T, N, P>;
54        fn bitand(self, other: FixedUInt<T, N, P>) -> Self::Output {
55            self.bitand(&other)
56        }
57    }
58
59    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitAndAssign for FixedUInt<T, N, P> {
60        fn bitand_assign(&mut self, other: Self) {
61            let mut i = 0;
62            while i < N {
63                self.array[i] &= other.array[i];
64                i += 1;
65            }
66        }
67    }
68
69    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitOr<&FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
70        type Output = FixedUInt<T, N, P>;
71        fn bitor(self, other: &FixedUInt<T, N, P>) -> Self::Output {
72            let mut ret = <FixedUInt<T, N, P> as ConstZero>::zero();
73            let mut i = 0;
74            while i < N {
75                ret.array[i] = self.array[i] | other.array[i];
76                i += 1;
77            }
78            ret
79        }
80    }
81
82    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitOr for FixedUInt<T, N, P> {
83        type Output = Self;
84        fn bitor(self, other: Self) -> Self::Output {
85            (&self).bitor(&other)
86        }
87    }
88
89    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitOr<&FixedUInt<T, N, P>> for FixedUInt<T, N, P> {
90        type Output = Self;
91        fn bitor(self, other: &FixedUInt<T, N, P>) -> Self::Output {
92            (&self).bitor(other)
93        }
94    }
95
96    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitOr<FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
97        type Output = FixedUInt<T, N, P>;
98        fn bitor(self, other: FixedUInt<T, N, P>) -> Self::Output {
99            self.bitor(&other)
100        }
101    }
102
103    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitOrAssign for FixedUInt<T, N, P> {
104        fn bitor_assign(&mut self, other: Self) {
105            let mut i = 0;
106            while i < N {
107                self.array[i] |= other.array[i];
108                i += 1;
109            }
110        }
111    }
112
113    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitXor<&FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
114        type Output = FixedUInt<T, N, P>;
115        fn bitxor(self, other: &FixedUInt<T, N, P>) -> Self::Output {
116            let mut ret = <FixedUInt<T, N, P> as ConstZero>::zero();
117            let mut i = 0;
118            while i < N {
119                ret.array[i] = self.array[i] ^ other.array[i];
120                i += 1;
121            }
122            ret
123        }
124    }
125
126    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitXor for FixedUInt<T, N, P> {
127        type Output = Self;
128        fn bitxor(self, other: Self) -> Self::Output {
129            (&self).bitxor(&other)
130        }
131    }
132
133    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitXor<&FixedUInt<T, N, P>> for FixedUInt<T, N, P> {
134        type Output = Self;
135        fn bitxor(self, other: &FixedUInt<T, N, P>) -> Self::Output {
136            (&self).bitxor(other)
137        }
138    }
139
140    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitXor<FixedUInt<T, N, P>> for &FixedUInt<T, N, P> {
141        type Output = FixedUInt<T, N, P>;
142        fn bitxor(self, other: FixedUInt<T, N, P>) -> Self::Output {
143            self.bitxor(&other)
144        }
145    }
146
147    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::BitXorAssign for FixedUInt<T, N, P> {
148        fn bitxor_assign(&mut self, other: Self) {
149            let mut i = 0;
150            while i < N {
151                self.array[i] ^= other.array[i];
152                i += 1;
153            }
154        }
155    }
156
157    // Primary Shl/Shr implementations
158    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<usize> for FixedUInt<T, N, P> {
159        type Output = Self;
160        fn shl(self, bits: usize) -> Self::Output {
161            let mut result = self;
162            match P::TAG {
163                PersonalityTag::Nct => const_shl_impl(&mut result, bits),
164                PersonalityTag::Ct => const_shl_ct(&mut result, bits),
165            }
166            result
167        }
168    }
169
170    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<usize> for FixedUInt<T, N, P> {
171        type Output = Self;
172        fn shr(self, bits: usize) -> Self::Output {
173            let mut result = self;
174            match P::TAG {
175                PersonalityTag::Nct => const_shr_impl(&mut result, bits),
176                PersonalityTag::Ct => const_shr_ct(&mut result, bits),
177            }
178            result
179        }
180    }
181
182    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<u32> for FixedUInt<T, N, P> {
183        type Output = Self;
184        fn shl(self, bits: u32) -> Self::Output {
185            const_unbounded_shl_u32(self, bits)
186        }
187    }
188
189    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<u32> for FixedUInt<T, N, P> {
190        type Output = Self;
191        fn shr(self, bits: u32) -> Self::Output {
192            const_unbounded_shr_u32(self, bits)
193        }
194    }
195
196    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<&usize> for FixedUInt<T, N, P> {
197        type Output = Self;
198        fn shl(self, bits: &usize) -> Self::Output {
199            self.shl(*bits)
200        }
201    }
202
203    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<&usize> for FixedUInt<T, N, P> {
204        type Output = Self;
205        fn shr(self, bits: &usize) -> Self::Output {
206            self.shr(*bits)
207        }
208    }
209
210    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<&u32> for FixedUInt<T, N, P> {
211        type Output = Self;
212        fn shl(self, bits: &u32) -> Self::Output {
213            self.shl(*bits)
214        }
215    }
216
217    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<&u32> for FixedUInt<T, N, P> {
218        type Output = Self;
219        fn shr(self, bits: &u32) -> Self::Output {
220            self.shr(*bits)
221        }
222    }
223
224    // Shl/Shr for &FixedUInt
225    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<usize> for &FixedUInt<T, N, P> {
226        type Output = FixedUInt<T, N, P>;
227        fn shl(self, bits: usize) -> Self::Output {
228            (*self).shl(bits)
229        }
230    }
231
232    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<usize> for &FixedUInt<T, N, P> {
233        type Output = FixedUInt<T, N, P>;
234        fn shr(self, bits: usize) -> Self::Output {
235            (*self).shr(bits)
236        }
237    }
238
239    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<u32> for &FixedUInt<T, N, P> {
240        type Output = FixedUInt<T, N, P>;
241        fn shl(self, bits: u32) -> Self::Output {
242            (*self).shl(bits)
243        }
244    }
245
246    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<u32> for &FixedUInt<T, N, P> {
247        type Output = FixedUInt<T, N, P>;
248        fn shr(self, bits: u32) -> Self::Output {
249            (*self).shr(bits)
250        }
251    }
252
253    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<&usize> for &FixedUInt<T, N, P> {
254        type Output = FixedUInt<T, N, P>;
255        fn shl(self, bits: &usize) -> Self::Output {
256            (*self).shl(*bits)
257        }
258    }
259
260    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<&usize> for &FixedUInt<T, N, P> {
261        type Output = FixedUInt<T, N, P>;
262        fn shr(self, bits: &usize) -> Self::Output {
263            (*self).shr(*bits)
264        }
265    }
266
267    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shl<&u32> for &FixedUInt<T, N, P> {
268        type Output = FixedUInt<T, N, P>;
269        fn shl(self, bits: &u32) -> Self::Output {
270            (*self).shl(*bits)
271        }
272    }
273
274    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::Shr<&u32> for &FixedUInt<T, N, P> {
275        type Output = FixedUInt<T, N, P>;
276        fn shr(self, bits: &u32) -> Self::Output {
277            (*self).shr(*bits)
278        }
279    }
280
281    // ShlAssign/ShrAssign
282    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::ShlAssign<usize> for FixedUInt<T, N, P> {
283        fn shl_assign(&mut self, bits: usize) {
284            match P::TAG {
285                PersonalityTag::Nct => const_shl_impl(self, bits),
286                PersonalityTag::Ct => const_shl_ct(self, bits),
287            }
288        }
289    }
290
291    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::ShrAssign<usize> for FixedUInt<T, N, P> {
292        fn shr_assign(&mut self, bits: usize) {
293            match P::TAG {
294                PersonalityTag::Nct => const_shr_impl(self, bits),
295                PersonalityTag::Ct => const_shr_ct(self, bits),
296            }
297        }
298    }
299
300    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::ShlAssign<&usize> for FixedUInt<T, N, P> {
301        fn shl_assign(&mut self, bits: &usize) {
302            match P::TAG {
303                PersonalityTag::Nct => const_shl_impl(self, *bits),
304                PersonalityTag::Ct => const_shl_ct(self, *bits),
305            }
306        }
307    }
308
309    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> core::ops::ShrAssign<&usize> for FixedUInt<T, N, P> {
310        fn shr_assign(&mut self, bits: &usize) {
311            match P::TAG {
312                PersonalityTag::Nct => const_shr_impl(self, *bits),
313                PersonalityTag::Ct => const_shr_ct(self, *bits),
314            }
315        }
316    }
317
318    // Shared body for `Shl<u32>` and `ConstUnboundedShift::unbounded_shl`.
319    // Both want the same semantics — shift by a u32 amount, with values
320    // outside [0, BIT_SIZE) collapsing to zero — and previously had
321    // parallel implementations. The Ct fix in PR #120 was applied to
322    // `unbounded_shl` but missed the `Shl<u32>` copy; centralizing here
323    // makes that class of drift impossible.
324    pub(crate) c0nst fn const_unbounded_shl_u32<
325        T: [c0nst] ConstMachineWord + MachineWord,
326        const N: usize,
327        P: Personality,
328    >(
329        target: FixedUInt<T, N, P>,
330        bits: u32,
331    ) -> FixedUInt<T, N, P> {
332        match P::TAG {
333            PersonalityTag::Nct => {
334                let (shift, overflow) =
335                    normalize_shift_amount(bits, FixedUInt::<T, N, P>::BIT_SIZE);
336                if overflow {
337                    <FixedUInt<T, N, P> as ConstZero>::zero()
338                } else {
339                    target << shift
340                }
341            }
342            PersonalityTag::Ct => {
343                // Skip `normalize_shift_amount` entirely. Its `if bits >=
344                // bit_size_u32` is a tainted-flag branch and `bits %
345                // bit_size_u32` is a variable-time modulo when bits is a
346                // secret — both leaks even though current LLVM may pattern-
347                // match them on power-of-2 BIT_SIZE. `const_shl_ct`'s
348                // barrel shifter already collapses out-of-range shifts to
349                // zero (via `const_shl_impl`'s `nwords >= N` zero-out), so
350                // the overflow detection is redundant for the Ct path —
351                // EXCEPT for the cast to usize on 16-bit-usize targets,
352                // where `bits as usize` truncates and could undo the
353                // saturation. Cap branchlessly to `BIT_SIZE` first; for
354                // every priority diagonal BIT_SIZE fits in u16, so the
355                // capped value casts losslessly even on AVR.
356                let bit_size_u32 = FixedUInt::<T, N, P>::BIT_SIZE as u32;
357                let capped = const_ct_min_u32(bits, bit_size_u32);
358                target << (capped as usize)
359            }
360        }
361    }
362
363    /// Mirror of [`const_unbounded_shl_u32`] for right-shifts.
364    pub(crate) c0nst fn const_unbounded_shr_u32<
365        T: [c0nst] ConstMachineWord + MachineWord,
366        const N: usize,
367        P: Personality,
368    >(
369        target: FixedUInt<T, N, P>,
370        bits: u32,
371    ) -> FixedUInt<T, N, P> {
372        match P::TAG {
373            PersonalityTag::Nct => {
374                let (shift, overflow) =
375                    normalize_shift_amount(bits, FixedUInt::<T, N, P>::BIT_SIZE);
376                if overflow {
377                    <FixedUInt<T, N, P> as ConstZero>::zero()
378                } else {
379                    target >> shift
380                }
381            }
382            PersonalityTag::Ct => {
383                // See `const_unbounded_shl_u32` for why this skips
384                // `normalize_shift_amount` and caps before casting.
385                let bit_size_u32 = FixedUInt::<T, N, P>::BIT_SIZE as u32;
386                let capped = const_ct_min_u32(bits, bit_size_u32);
387                target >> (capped as usize)
388            }
389        }
390    }
391
392    /// Branchless CT-safe `min(bits, cap)` for u32, used to clamp Ct
393    /// shift amounts to `BIT_SIZE` before casting to usize. The
394    /// `black_box` on the mask is load-bearing — without it, LLVM
395    /// recognises the XOR-AND-XOR select idiom and rewrites it into a
396    /// `cmov` whose flag depends on the secret `bits`. Same defence as
397    /// `const_ct_select` (PR #118).
398    c0nst fn const_ct_min_u32(bits: u32, cap: u32) -> u32 {
399        // diff = cap - bits, wraps to negative (high bit set) iff bits > cap.
400        let diff = cap.wrapping_sub(bits);
401        let too_big_bit = (diff >> 31) & 1;
402        let too_big_mask = core::hint::black_box(too_big_bit.wrapping_neg());
403        // bits if !too_big, cap otherwise. XOR-AND-XOR select with
404        // opaque mask.
405        bits ^ (too_big_mask & (bits ^ cap))
406    }
407
408    // Helper to normalize shift amount and detect overflow.
409    // Handles both 16-bit (usize < u32) and 64-bit (bit_size > u32::MAX) platforms.
410    c0nst fn normalize_shift_amount(bits: u32, bit_size: usize) -> (usize, bool) {
411        let bit_size_u32 = bit_size as u32;
412        if bit_size == 0 {
413            // Zero-size type: always overflow
414            (0, true)
415        } else if bit_size_u32 == 0 {
416            // bit_size is a non-zero multiple of 2^32 (huge type on 64-bit).
417            // Since bits is u32, it's always smaller than bit_size. No overflow.
418            (bits as usize, false)
419        } else if bits >= bit_size_u32 {
420            // Normal case: shift exceeds bit width
421            ((bits % bit_size_u32) as usize, true)
422        } else {
423            (bits as usize, false)
424        }
425    }
426
427    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> ConstOverflowingShl for FixedUInt<T, N, P> {
428        fn overflowing_shl(&self, bits: u32) -> (Self, bool) {
429            let (shift, overflow) = normalize_shift_amount(bits, Self::BIT_SIZE);
430            let res = core::ops::Shl::<usize>::shl(*self, shift);
431            (res, overflow)
432        }
433    }
434
435    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> ConstOverflowingShr for FixedUInt<T, N, P> {
436        fn overflowing_shr(&self, bits: u32) -> (Self, bool) {
437            let (shift, overflow) = normalize_shift_amount(bits, Self::BIT_SIZE);
438            let res = core::ops::Shr::<usize>::shr(*self, shift);
439            (res, overflow)
440        }
441    }
442
443    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> ConstWrappingShl for FixedUInt<T, N, P> {
444        fn wrapping_shl(&self, bits: u32) -> Self {
445            ConstOverflowingShl::overflowing_shl(self, bits).0
446        }
447    }
448
449    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> ConstWrappingShr for FixedUInt<T, N, P> {
450        fn wrapping_shr(&self, bits: u32) -> Self {
451            ConstOverflowingShr::overflowing_shr(self, bits).0
452        }
453    }
454
455    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> ConstCheckedShl for FixedUInt<T, N, P> {
456        fn checked_shl(&self, bits: u32) -> Option<Self> {
457            let (res, overflow) = ConstOverflowingShl::overflowing_shl(self, bits);
458            if overflow { None } else { Some(res) }
459        }
460    }
461
462    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> ConstCheckedShr for FixedUInt<T, N, P> {
463        fn checked_shr(&self, bits: u32) -> Option<Self> {
464            let (res, overflow) = ConstOverflowingShr::overflowing_shr(self, bits);
465            if overflow { None } else { Some(res) }
466        }
467    }
468
469    c0nst impl<T: [c0nst] ConstMachineWord + MachineWord, const N: usize, P: Personality> ConstUnboundedShift for FixedUInt<T, N, P> {
470        fn unbounded_shl(self, rhs: u32) -> Self {
471            const_unbounded_shl_u32(self, rhs)
472        }
473
474        fn unbounded_shr(self, rhs: u32) -> Self {
475            const_unbounded_shr_u32(self, rhs)
476        }
477    }
478}
479
480// OverflowingShl/Shr from patch_num_traits - delegate to const impls
481impl<T: MachineWord, const N: usize, P: Personality> OverflowingShl for FixedUInt<T, N, P> {
482    fn overflowing_shl(self, bits: u32) -> (Self, bool) {
483        ConstOverflowingShl::overflowing_shl(&self, bits)
484    }
485}
486
487impl<T: MachineWord, const N: usize, P: Personality> OverflowingShr for FixedUInt<T, N, P> {
488    fn overflowing_shr(self, bits: u32) -> (Self, bool) {
489        ConstOverflowingShr::overflowing_shr(&self, bits)
490    }
491}
492
493// num_traits wrappers - delegate to const impls
494impl<T: MachineWord, const N: usize, P: Personality> num_traits::WrappingShl
495    for FixedUInt<T, N, P>
496{
497    fn wrapping_shl(&self, bits: u32) -> Self {
498        ConstWrappingShl::wrapping_shl(self, bits)
499    }
500}
501
502impl<T: MachineWord, const N: usize, P: Personality> num_traits::WrappingShr
503    for FixedUInt<T, N, P>
504{
505    fn wrapping_shr(&self, bits: u32) -> Self {
506        ConstWrappingShr::wrapping_shr(self, bits)
507    }
508}
509
510impl<T: MachineWord, const N: usize, P: Personality> num_traits::CheckedShl for FixedUInt<T, N, P> {
511    fn checked_shl(&self, bits: u32) -> Option<Self> {
512        ConstCheckedShl::checked_shl(self, bits)
513    }
514}
515
516impl<T: MachineWord, const N: usize, P: Personality> num_traits::CheckedShr for FixedUInt<T, N, P> {
517    fn checked_shr(&self, bits: u32) -> Option<Self> {
518        ConstCheckedShr::checked_shr(self, bits)
519    }
520}
521
522#[cfg(test)]
523mod tests {
524    use super::*;
525
526    #[test]
527    fn test_bitand_combinations() {
528        let a = FixedUInt::<u8, 2>::from(12u8); // 1100
529        let b = FixedUInt::<u8, 2>::from(10u8); // 1010
530        let expected = FixedUInt::<u8, 2>::from(8u8); // 1000
531
532        // value & value
533        assert_eq!(a & b, expected);
534        // value & ref
535        assert_eq!(a & &b, expected);
536        // ref & value
537        assert_eq!(&a & b, expected);
538        // ref & ref
539        assert_eq!(&a & &b, expected);
540    }
541
542    #[test]
543    fn test_bitor_combinations() {
544        let a = FixedUInt::<u8, 2>::from(12u8); // 1100
545        let b = FixedUInt::<u8, 2>::from(10u8); // 1010
546        let expected = FixedUInt::<u8, 2>::from(14u8); // 1110
547
548        // value | value
549        assert_eq!(a | b, expected);
550        // value | ref
551        assert_eq!(a | &b, expected);
552        // ref | value
553        assert_eq!(&a | b, expected);
554        // ref | ref
555        assert_eq!(&a | &b, expected);
556    }
557
558    #[test]
559    fn test_bitxor_combinations() {
560        let a = FixedUInt::<u8, 2>::from(12u8); // 1100
561        let b = FixedUInt::<u8, 2>::from(10u8); // 1010
562        let expected = FixedUInt::<u8, 2>::from(6u8); // 0110
563
564        // value ^ value
565        assert_eq!(a ^ b, expected);
566        // value ^ ref
567        assert_eq!(a ^ &b, expected);
568        // ref ^ value
569        assert_eq!(&a ^ b, expected);
570        // ref ^ ref
571        assert_eq!(&a ^ &b, expected);
572    }
573
574    #[test]
575    fn test_shl_combinations() {
576        let a = FixedUInt::<u8, 2>::from(2u8); // 0010
577        let shift: usize = 2;
578        let expected = FixedUInt::<u8, 2>::from(8u8); // 1000
579
580        // value << value
581        assert_eq!(a << shift, expected);
582        // value << ref
583        assert_eq!(a << &shift, expected);
584        // ref << value
585        assert_eq!(&a << shift, expected);
586        // ref << ref
587        assert_eq!(&a << &shift, expected);
588
589        // Same with u32
590        let shift32: u32 = 2;
591        assert_eq!(a << shift32, expected);
592        assert_eq!(a << &shift32, expected);
593        assert_eq!(&a << shift32, expected);
594        assert_eq!(&a << &shift32, expected);
595    }
596
597    #[test]
598    fn test_shr_combinations() {
599        let a = FixedUInt::<u8, 2>::from(8u8); // 1000
600        let shift: usize = 2;
601        let expected = FixedUInt::<u8, 2>::from(2u8); // 0010
602
603        // value >> value
604        assert_eq!(a >> shift, expected);
605        // value >> ref
606        assert_eq!(a >> &shift, expected);
607        // ref >> value
608        assert_eq!(&a >> shift, expected);
609        // ref >> ref
610        assert_eq!(&a >> &shift, expected);
611
612        // Same with u32
613        let shift32: u32 = 2;
614        assert_eq!(a >> shift32, expected);
615        assert_eq!(a >> &shift32, expected);
616        assert_eq!(&a >> shift32, expected);
617        assert_eq!(&a >> &shift32, expected);
618    }
619
620    #[test]
621    fn test_const_bitops() {
622        type TestInt = FixedUInt<u8, 2>;
623
624        let a = TestInt::from(0b11001100u8);
625        let b = TestInt::from(0b10101010u8);
626
627        // Test not
628        let not_a = !a;
629        assert_eq!(not_a.array[0], 0b00110011);
630        assert_eq!(not_a.array[1], 0xFF);
631
632        // Test bitand
633        assert_eq!(a & b, TestInt::from(0b10001000u8));
634
635        // Test bitor
636        assert_eq!(a | b, TestInt::from(0b11101110u8));
637
638        // Test bitxor
639        assert_eq!(a ^ b, TestInt::from(0b01100110u8));
640
641        // Test shl
642        assert_eq!(TestInt::from(1u8) << 4usize, TestInt::from(16u8));
643
644        // Test shr
645        assert_eq!(TestInt::from(16u8) >> 2usize, TestInt::from(4u8));
646
647        #[cfg(feature = "nightly")]
648        {
649            const A: TestInt = FixedUInt::from_array([0b11001100, 0]);
650            const B: TestInt = FixedUInt::from_array([0b10101010, 0]);
651
652            const NOT_A: TestInt = !A;
653            const AND_AB: TestInt = A & B;
654            const OR_AB: TestInt = A | B;
655            const XOR_AB: TestInt = A ^ B;
656            const SHL_1: TestInt = FixedUInt::from_array([1u8, 0]) << 4usize;
657            const SHR_16: TestInt = FixedUInt::from_array([16u8, 0]) >> 2usize;
658
659            assert_eq!(NOT_A.array[0], 0b00110011);
660            assert_eq!(AND_AB.array[0], 0b10001000);
661            assert_eq!(OR_AB.array[0], 0b11101110);
662            assert_eq!(XOR_AB.array[0], 0b01100110);
663            assert_eq!(SHL_1.array[0], 16);
664            assert_eq!(SHR_16.array[0], 4);
665        }
666    }
667
668    #[test]
669    fn test_const_shift_traits() {
670        type TestInt = FixedUInt<u8, 2>; // 16-bit
671
672        // Test overflowing_shl
673        let a = TestInt::from(0x80u8); // 0x0080
674        let (res, overflow) = ConstOverflowingShl::overflowing_shl(&a, 8);
675        assert_eq!(res.array, [0, 0x80]); // 0x8000
676        assert!(!overflow);
677
678        let (res, overflow) = ConstOverflowingShl::overflowing_shl(&a, 16);
679        assert_eq!(res.array, [0x80, 0]); // wraps around
680        assert!(overflow);
681
682        let (res, overflow) = ConstOverflowingShl::overflowing_shl(&a, 9);
683        assert_eq!(res.array, [0, 0]); // high bits shifted out (but shift < bit_width)
684        assert!(!overflow); // 9 < 16, so no overflow
685
686        // Test overflowing_shr
687        let b = TestInt::from(0x0100u16); // 0x0100
688        let (res, overflow) = ConstOverflowingShr::overflowing_shr(&b, 8);
689        assert_eq!(res.array, [1, 0]); // 0x0001
690        assert!(!overflow);
691
692        let (res, overflow) = ConstOverflowingShr::overflowing_shr(&b, 16);
693        assert_eq!(res.array, [0, 1]); // wraps
694        assert!(overflow);
695
696        // Test wrapping_shl
697        let c = TestInt::from(1u8);
698        assert_eq!(ConstWrappingShl::wrapping_shl(&c, 4).array, [16, 0]);
699        assert_eq!(ConstWrappingShl::wrapping_shl(&c, 16).array, [1, 0]); // wraps
700        assert_eq!(ConstWrappingShl::wrapping_shl(&c, 17).array, [2, 0]); // wraps
701
702        // Test wrapping_shr
703        let d = TestInt::from(0x8000u16);
704        assert_eq!(ConstWrappingShr::wrapping_shr(&d, 4).array, [0, 0x08]);
705        assert_eq!(ConstWrappingShr::wrapping_shr(&d, 16).array, [0, 0x80]); // wraps
706        assert_eq!(ConstWrappingShr::wrapping_shr(&d, 17).array, [0, 0x40]); // wraps
707
708        // Test checked_shl
709        let e = TestInt::from(1u8);
710        assert_eq!(
711            ConstCheckedShl::checked_shl(&e, 4),
712            Some(TestInt::from(16u8))
713        );
714        assert_eq!(
715            ConstCheckedShl::checked_shl(&e, 15),
716            Some(TestInt::from(0x8000u16))
717        );
718        assert_eq!(ConstCheckedShl::checked_shl(&e, 16), None); // overflow
719
720        // Test checked_shr
721        let f = TestInt::from(0x8000u16);
722        assert_eq!(
723            ConstCheckedShr::checked_shr(&f, 15),
724            Some(TestInt::from(1u8))
725        );
726        assert_eq!(ConstCheckedShr::checked_shr(&f, 16), None); // overflow
727
728        // Test edge case: zero shift
729        let g = TestInt::from(42u8);
730        assert_eq!(ConstOverflowingShl::overflowing_shl(&g, 0), (g, false));
731        assert_eq!(ConstOverflowingShr::overflowing_shr(&g, 0), (g, false));
732        assert_eq!(ConstWrappingShl::wrapping_shl(&g, 0), g);
733        assert_eq!(ConstWrappingShr::wrapping_shr(&g, 0), g);
734        assert_eq!(ConstCheckedShl::checked_shl(&g, 0), Some(g));
735        assert_eq!(ConstCheckedShr::checked_shr(&g, 0), Some(g));
736    }
737
738    #[test]
739    fn test_const_shift_traits_n0() {
740        // Test with N=0 (zero-sized type)
741        type ZeroInt = FixedUInt<u8, 0>;
742        let z = ZeroInt::from_array([]);
743
744        // All shifts on zero-sized type should overflow
745        assert_eq!(ConstOverflowingShl::overflowing_shl(&z, 0), (z, true));
746        assert_eq!(ConstOverflowingShr::overflowing_shr(&z, 0), (z, true));
747        assert_eq!(ConstWrappingShl::wrapping_shl(&z, 0), z);
748        assert_eq!(ConstWrappingShr::wrapping_shr(&z, 0), z);
749        assert_eq!(ConstCheckedShl::checked_shl(&z, 0), None);
750        assert_eq!(ConstCheckedShr::checked_shr(&z, 0), None);
751    }
752
753    #[test]
754    fn test_num_traits_shift_wrappers() {
755        use num_traits::{CheckedShl, CheckedShr, WrappingShl, WrappingShr};
756
757        type TestInt = FixedUInt<u8, 2>;
758
759        let a = TestInt::from(1u8);
760
761        // num_traits::WrappingShl delegates to ConstWrappingShl
762        assert_eq!(WrappingShl::wrapping_shl(&a, 4), TestInt::from(16u8));
763        assert_eq!(WrappingShl::wrapping_shl(&a, 16), a); // wraps
764
765        // num_traits::WrappingShr
766        let b = TestInt::from(16u8);
767        assert_eq!(WrappingShr::wrapping_shr(&b, 4), TestInt::from(1u8));
768
769        // num_traits::CheckedShl
770        assert_eq!(CheckedShl::checked_shl(&a, 4), Some(TestInt::from(16u8)));
771        assert_eq!(CheckedShl::checked_shl(&a, 16), None);
772
773        // num_traits::CheckedShr
774        assert_eq!(CheckedShr::checked_shr(&b, 4), Some(TestInt::from(1u8)));
775        assert_eq!(CheckedShr::checked_shr(&b, 16), None);
776    }
777
778    #[test]
779    fn test_unbounded_shift() {
780        type U16 = FixedUInt<u8, 2>;
781
782        let one = U16::from(1u8);
783
784        // Normal shifts (within bounds)
785        assert_eq!(ConstUnboundedShift::unbounded_shl(one, 0), one);
786        assert_eq!(ConstUnboundedShift::unbounded_shl(one, 4), U16::from(16u8));
787        assert_eq!(
788            ConstUnboundedShift::unbounded_shl(one, 15),
789            U16::from(0x8000u16)
790        );
791
792        assert_eq!(
793            ConstUnboundedShift::unbounded_shr(U16::from(0x8000u16), 15),
794            one
795        );
796        assert_eq!(ConstUnboundedShift::unbounded_shr(U16::from(16u8), 4), one);
797
798        // At boundary (shift by bit width) - returns 0
799        assert_eq!(ConstUnboundedShift::unbounded_shl(one, 16), U16::from(0u8));
800        assert_eq!(
801            ConstUnboundedShift::unbounded_shr(U16::from(0xFFFFu16), 16),
802            U16::from(0u8)
803        );
804
805        // Beyond boundary - returns 0
806        assert_eq!(
807            ConstUnboundedShift::unbounded_shl(U16::from(0xFFFFu16), 17),
808            U16::from(0u8)
809        );
810        assert_eq!(
811            ConstUnboundedShift::unbounded_shl(U16::from(0xFFFFu16), 100),
812            U16::from(0u8)
813        );
814        assert_eq!(
815            ConstUnboundedShift::unbounded_shr(U16::from(0xFFFFu16), 17),
816            U16::from(0u8)
817        );
818        assert_eq!(
819            ConstUnboundedShift::unbounded_shr(U16::from(0xFFFFu16), 100),
820            U16::from(0u8)
821        );
822
823        // Test with different word sizes
824        type U32 = FixedUInt<u8, 4>;
825        let one32 = U32::from(1u8);
826        assert_eq!(
827            ConstUnboundedShift::unbounded_shl(one32, 31),
828            U32::from(0x80000000u32)
829        );
830        assert_eq!(
831            ConstUnboundedShift::unbounded_shl(one32, 32),
832            U32::from(0u8)
833        );
834        assert_eq!(
835            ConstUnboundedShift::unbounded_shr(U32::from(0x80000000u32), 31),
836            one32
837        );
838        assert_eq!(
839            ConstUnboundedShift::unbounded_shr(U32::from(0x80000000u32), 32),
840            U32::from(0u8)
841        );
842    }
843
844    #[test]
845    fn test_unbounded_shift_polymorphic() {
846        fn test_unbounded<T>(val: T, shift: u32, expected_shl: T, expected_shr: T)
847        where
848            T: ConstUnboundedShift + Eq + core::fmt::Debug + Copy,
849        {
850            assert_eq!(ConstUnboundedShift::unbounded_shl(val, shift), expected_shl);
851            assert_eq!(ConstUnboundedShift::unbounded_shr(val, shift), expected_shr);
852        }
853
854        // Test with FixedUInt layouts
855        type U8x2 = FixedUInt<u8, 2>;
856        type U8x4 = FixedUInt<u8, 4>;
857        type U16x2 = FixedUInt<u16, 2>;
858
859        // Same logical shift, different layouts
860        test_unbounded(U8x2::from(1u8), 4, U8x2::from(16u8), U8x2::from(0u8));
861        test_unbounded(U8x4::from(1u8), 4, U8x4::from(16u8), U8x4::from(0u8));
862        test_unbounded(U16x2::from(1u8), 4, U16x2::from(16u8), U16x2::from(0u8));
863
864        // Test with primitives
865        test_unbounded(1u8, 4, 16u8, 0u8);
866        test_unbounded(1u16, 4, 16u16, 0u16);
867        test_unbounded(1u32, 4, 16u32, 0u32);
868
869        // Boundary tests
870        test_unbounded(1u8, 8, 0u8, 0u8);
871        test_unbounded(U8x2::from(1u8), 16, U8x2::from(0u8), U8x2::from(0u8));
872    }
873}