Skip to main content

fixed_bigint/heapless/
shift_ops.rs

1//! The shift-family traits for `HeaplessBigInt`: overflowing / wrapping /
2//! checked / unbounded / exact / funnel shifts, plus the `num_traits`
3//! wrapping/checked wrappers.
4//!
5//! All build on the inherent `<<` / `>>`, so they inherit those width
6//! contracts: the left-shift family is width-preserving (`out_len =
7//! self.len`, bits past the width discarded), the right-shift family follows
8//! `Shr`'s whole-word narrowing (`out_len = self.len - bits/word_bits`). The
9//! shift amount is a public `u32`, so every arm is personality-generic.
10//!
11//! The "overflow" flag / `None` / wrap is purely about the *amount* (`bits >=
12//! value_width`), exactly like the primitive `overflowing_shl` — it is not a
13//! value predicate, so it stays uniform across `Nct`/`Ct`.
14
15use super::HeaplessBigInt;
16use crate::MachineWord;
17use const_num_traits::{
18    CheckedShl, CheckedShr, FunnelShl, FunnelShr, OverflowingShl, OverflowingShr, Personality,
19    PersonalityTag, PrimBits, ShlExact, ShrExact, UnboundedShl, UnboundedShr, WrappingShl,
20    WrappingShr,
21};
22
23impl<T: MachineWord, const CAP: usize, P: Personality> HeaplessBigInt<T, CAP, P> {
24    /// Operating width in bits (`len · word_bits`). Fits `u32` for any
25    /// representable `len` (≤ `u16::MAX`) and word size (≤ 64).
26    #[inline]
27    fn value_bits(&self) -> u32 {
28        self.len as u32 * (core::mem::size_of::<T>() as u32 * 8)
29    }
30}
31
32/// `(normalized_shift, overflowed)` — mirrors the primitive masking. A
33/// zero-width value (`len == 0`) always overflows.
34#[inline]
35fn normalize_shift(bits: u32, value_bits: u32) -> (usize, bool) {
36    if value_bits == 0 {
37        (0, true)
38    } else if bits >= value_bits {
39        ((bits % value_bits) as usize, true)
40    } else {
41        (bits as usize, false)
42    }
43}
44
45impl<T: MachineWord, const CAP: usize, P: Personality> OverflowingShl
46    for HeaplessBigInt<T, CAP, P>
47{
48    type Output = Self;
49    fn overflowing_shl(self, bits: u32) -> (Self, bool) {
50        let (shift, overflow) = normalize_shift(bits, self.value_bits());
51        (self << shift, overflow)
52    }
53}
54
55impl<T: MachineWord, const CAP: usize, P: Personality> OverflowingShr
56    for HeaplessBigInt<T, CAP, P>
57{
58    type Output = Self;
59    fn overflowing_shr(self, bits: u32) -> (Self, bool) {
60        let (shift, overflow) = normalize_shift(bits, self.value_bits());
61        (self >> shift, overflow)
62    }
63}
64
65impl<T: MachineWord, const CAP: usize, P: Personality> WrappingShl for HeaplessBigInt<T, CAP, P> {
66    type Output = Self;
67    fn wrapping_shl(self, bits: u32) -> Self {
68        OverflowingShl::overflowing_shl(self, bits).0
69    }
70}
71
72impl<T: MachineWord, const CAP: usize, P: Personality> WrappingShr for HeaplessBigInt<T, CAP, P> {
73    type Output = Self;
74    fn wrapping_shr(self, bits: u32) -> Self {
75        OverflowingShr::overflowing_shr(self, bits).0
76    }
77}
78
79impl<T: MachineWord, const CAP: usize, P: Personality> CheckedShl for HeaplessBigInt<T, CAP, P> {
80    type Output = Self;
81    fn checked_shl(self, bits: u32) -> Option<Self> {
82        let (res, overflow) = OverflowingShl::overflowing_shl(self, bits);
83        if overflow { None } else { Some(res) }
84    }
85}
86
87impl<T: MachineWord, const CAP: usize, P: Personality> CheckedShr for HeaplessBigInt<T, CAP, P> {
88    type Output = Self;
89    fn checked_shr(self, bits: u32) -> Option<Self> {
90        let (res, overflow) = OverflowingShr::overflowing_shr(self, bits);
91        if overflow { None } else { Some(res) }
92    }
93}
94
95// Unbounded: shift by any amount, saturating to zero past the width. The
96// inherent `<<` / `>>` already collapse to zero once the whole-word shift
97// clears every limb, so an over-width amount is handled directly.
98impl<T: MachineWord, const CAP: usize, P: Personality> UnboundedShl for HeaplessBigInt<T, CAP, P> {
99    type Output = Self;
100    fn unbounded_shl(self, rhs: u32) -> Self {
101        match P::TAG {
102            // Ct: the over-width guard would branch on the (secret) amount;
103            // the Ct `<<` barrel already collapses over-width shifts to zero.
104            PersonalityTag::Ct => self << (rhs as usize),
105            PersonalityTag::Nct => {
106                if rhs >= self.value_bits() {
107                    Self::new_zero_with_len(self.len())
108                } else {
109                    self << (rhs as usize)
110                }
111            }
112        }
113    }
114}
115
116impl<T: MachineWord, const CAP: usize, P: Personality> UnboundedShr for HeaplessBigInt<T, CAP, P> {
117    type Output = Self;
118    fn unbounded_shr(self, rhs: u32) -> Self {
119        match P::TAG {
120            PersonalityTag::Ct => self >> (rhs as usize),
121            PersonalityTag::Nct => {
122                if rhs >= self.value_bits() {
123                    Self::new_zero_with_len(self.len())
124                } else {
125                    self >> (rhs as usize)
126                }
127            }
128        }
129    }
130}
131
132// Exact (lossless) shifts: `None` if any one-bit would be shifted out or the
133// amount reaches the value width.
134impl<T: MachineWord, const CAP: usize, P: Personality> ShlExact for HeaplessBigInt<T, CAP, P> {
135    type Output = Self;
136    fn shl_exact(self, rhs: u32) -> Option<Self> {
137        if rhs < self.value_bits() && rhs <= PrimBits::leading_zeros(self) {
138            Some(self << (rhs as usize))
139        } else {
140            None
141        }
142    }
143}
144
145impl<T: MachineWord, const CAP: usize, P: Personality> ShrExact for HeaplessBigInt<T, CAP, P> {
146    type Output = Self;
147    fn shr_exact(self, rhs: u32) -> Option<Self> {
148        if rhs < self.value_bits() && rhs <= PrimBits::trailing_zeros(self) {
149            // A whole-limb `>>` narrows `len`; an *exact* (reversible) shift
150            // must keep the operand width so `<<` by the same amount recovers
151            // the input. No bits are lost (the trailing-zero check passed), so
152            // widening back is value-preserving.
153            let width = self.len();
154            Some((self >> (rhs as usize)).widened(width))
155        } else {
156            None
157        }
158    }
159}
160
161// Funnel shifts: the double-width `(self, rhs)` shifted by `n`, one half
162// returned. Both halves are taken at `self`'s width — `rhs` must share it, or
163// its high limbs (outside the funnel word) would leak into the result — so a
164// width mismatch is a caller error, asserted for `n > 0`. `n == 0` is a no-op
165// checked first, so it never trips the width/range asserts (a `len == 0`
166// operand has `value_bits() == 0`, against which `n < bits` would fail).
167impl<T: MachineWord, const CAP: usize, P: Personality> FunnelShl for HeaplessBigInt<T, CAP, P> {
168    type Output = Self;
169    fn funnel_shl(self, rhs: Self, n: u32) -> Self {
170        if n == 0 {
171            return self;
172        }
173        assert!(
174            self.len() == rhs.len(),
175            "HeaplessBigInt::funnel_shl: operands must share a width"
176        );
177        let bits = self.value_bits();
178        assert!(n < bits, "HeaplessBigInt::funnel_shl: n out of range");
179        let lo_shift = bits - n;
180        (self << (n as usize)) | (rhs >> (lo_shift as usize))
181    }
182}
183
184impl<T: MachineWord, const CAP: usize, P: Personality> FunnelShr for HeaplessBigInt<T, CAP, P> {
185    type Output = Self;
186    fn funnel_shr(self, rhs: Self, n: u32) -> Self {
187        if n == 0 {
188            return rhs;
189        }
190        assert!(
191            self.len() == rhs.len(),
192            "HeaplessBigInt::funnel_shr: operands must share a width"
193        );
194        let bits = self.value_bits();
195        assert!(n < bits, "HeaplessBigInt::funnel_shr: n out of range");
196        let hi_shift = bits - n;
197        (rhs >> (n as usize)) | (self << (hi_shift as usize))
198    }
199}
200
201// Reference-receiver mirrors (`&HeaplessBigInt`), so `(&h).wrapping_shl(n)` etc.
202// resolve. `HeaplessBigInt` is `Copy`; each delegates to the value impl on
203// `*self` (and `*rhs` for the funnel operand). `u32` amounts pass through.
204
205impl<T: MachineWord, const CAP: usize, P: Personality> OverflowingShl
206    for &HeaplessBigInt<T, CAP, P>
207{
208    type Output = HeaplessBigInt<T, CAP, P>;
209    fn overflowing_shl(self, bits: u32) -> (HeaplessBigInt<T, CAP, P>, bool) {
210        <HeaplessBigInt<T, CAP, P> as OverflowingShl>::overflowing_shl(*self, bits)
211    }
212}
213
214impl<T: MachineWord, const CAP: usize, P: Personality> OverflowingShr
215    for &HeaplessBigInt<T, CAP, P>
216{
217    type Output = HeaplessBigInt<T, CAP, P>;
218    fn overflowing_shr(self, bits: u32) -> (HeaplessBigInt<T, CAP, P>, bool) {
219        <HeaplessBigInt<T, CAP, P> as OverflowingShr>::overflowing_shr(*self, bits)
220    }
221}
222
223impl<T: MachineWord, const CAP: usize, P: Personality> WrappingShl for &HeaplessBigInt<T, CAP, P> {
224    type Output = HeaplessBigInt<T, CAP, P>;
225    fn wrapping_shl(self, bits: u32) -> HeaplessBigInt<T, CAP, P> {
226        <HeaplessBigInt<T, CAP, P> as WrappingShl>::wrapping_shl(*self, bits)
227    }
228}
229
230impl<T: MachineWord, const CAP: usize, P: Personality> WrappingShr for &HeaplessBigInt<T, CAP, P> {
231    type Output = HeaplessBigInt<T, CAP, P>;
232    fn wrapping_shr(self, bits: u32) -> HeaplessBigInt<T, CAP, P> {
233        <HeaplessBigInt<T, CAP, P> as WrappingShr>::wrapping_shr(*self, bits)
234    }
235}
236
237impl<T: MachineWord, const CAP: usize, P: Personality> CheckedShl for &HeaplessBigInt<T, CAP, P> {
238    type Output = HeaplessBigInt<T, CAP, P>;
239    fn checked_shl(self, bits: u32) -> Option<HeaplessBigInt<T, CAP, P>> {
240        <HeaplessBigInt<T, CAP, P> as CheckedShl>::checked_shl(*self, bits)
241    }
242}
243
244impl<T: MachineWord, const CAP: usize, P: Personality> CheckedShr for &HeaplessBigInt<T, CAP, P> {
245    type Output = HeaplessBigInt<T, CAP, P>;
246    fn checked_shr(self, bits: u32) -> Option<HeaplessBigInt<T, CAP, P>> {
247        <HeaplessBigInt<T, CAP, P> as CheckedShr>::checked_shr(*self, bits)
248    }
249}
250
251impl<T: MachineWord, const CAP: usize, P: Personality> UnboundedShl for &HeaplessBigInt<T, CAP, P> {
252    type Output = HeaplessBigInt<T, CAP, P>;
253    fn unbounded_shl(self, rhs: u32) -> HeaplessBigInt<T, CAP, P> {
254        <HeaplessBigInt<T, CAP, P> as UnboundedShl>::unbounded_shl(*self, rhs)
255    }
256}
257
258impl<T: MachineWord, const CAP: usize, P: Personality> UnboundedShr for &HeaplessBigInt<T, CAP, P> {
259    type Output = HeaplessBigInt<T, CAP, P>;
260    fn unbounded_shr(self, rhs: u32) -> HeaplessBigInt<T, CAP, P> {
261        <HeaplessBigInt<T, CAP, P> as UnboundedShr>::unbounded_shr(*self, rhs)
262    }
263}
264
265impl<T: MachineWord, const CAP: usize, P: Personality> ShlExact for &HeaplessBigInt<T, CAP, P> {
266    type Output = HeaplessBigInt<T, CAP, P>;
267    fn shl_exact(self, rhs: u32) -> Option<HeaplessBigInt<T, CAP, P>> {
268        <HeaplessBigInt<T, CAP, P> as ShlExact>::shl_exact(*self, rhs)
269    }
270}
271
272impl<T: MachineWord, const CAP: usize, P: Personality> ShrExact for &HeaplessBigInt<T, CAP, P> {
273    type Output = HeaplessBigInt<T, CAP, P>;
274    fn shr_exact(self, rhs: u32) -> Option<HeaplessBigInt<T, CAP, P>> {
275        <HeaplessBigInt<T, CAP, P> as ShrExact>::shr_exact(*self, rhs)
276    }
277}
278
279impl<T: MachineWord, const CAP: usize, P: Personality> FunnelShl for &HeaplessBigInt<T, CAP, P> {
280    type Output = HeaplessBigInt<T, CAP, P>;
281    fn funnel_shl(self, rhs: Self, n: u32) -> HeaplessBigInt<T, CAP, P> {
282        <HeaplessBigInt<T, CAP, P> as FunnelShl>::funnel_shl(*self, *rhs, n)
283    }
284}
285
286impl<T: MachineWord, const CAP: usize, P: Personality> FunnelShr for &HeaplessBigInt<T, CAP, P> {
287    type Output = HeaplessBigInt<T, CAP, P>;
288    fn funnel_shr(self, rhs: Self, n: u32) -> HeaplessBigInt<T, CAP, P> {
289        <HeaplessBigInt<T, CAP, P> as FunnelShr>::funnel_shr(*self, *rhs, n)
290    }
291}
292
293// num_traits wrappers — delegate to the const-num-traits impls above.
294#[cfg(feature = "num-traits")]
295impl<T: MachineWord, const CAP: usize, P: Personality> num_traits::WrappingShl
296    for HeaplessBigInt<T, CAP, P>
297{
298    fn wrapping_shl(&self, bits: u32) -> Self {
299        <Self as WrappingShl>::wrapping_shl(*self, bits)
300    }
301}
302
303#[cfg(feature = "num-traits")]
304impl<T: MachineWord, const CAP: usize, P: Personality> num_traits::WrappingShr
305    for HeaplessBigInt<T, CAP, P>
306{
307    fn wrapping_shr(&self, bits: u32) -> Self {
308        <Self as WrappingShr>::wrapping_shr(*self, bits)
309    }
310}
311
312#[cfg(feature = "num-traits")]
313impl<T: MachineWord, const CAP: usize, P: Personality> num_traits::CheckedShl
314    for HeaplessBigInt<T, CAP, P>
315{
316    fn checked_shl(&self, bits: u32) -> Option<Self> {
317        <Self as CheckedShl>::checked_shl(*self, bits)
318    }
319}
320
321#[cfg(feature = "num-traits")]
322impl<T: MachineWord, const CAP: usize, P: Personality> num_traits::CheckedShr
323    for HeaplessBigInt<T, CAP, P>
324{
325    fn checked_shr(&self, bits: u32) -> Option<Self> {
326        <Self as CheckedShr>::checked_shr(*self, bits)
327    }
328}
329
330#[cfg(test)]
331mod tests {
332    use super::HeaplessBigInt;
333    use const_num_traits::{
334        CheckedShl, CheckedShr, FunnelShl, FunnelShr, OverflowingShl, ShlExact, ShrExact,
335        UnboundedShl, UnboundedShr, WrappingShl,
336    };
337
338    type H = HeaplessBigInt<u8, 4>; // 32-bit width at len 4
339
340    #[test]
341    fn overflowing_wrapping_checked() {
342        let v = H::from(1u8).widened(4);
343        // In-range shift, no overflow.
344        assert_eq!(
345            OverflowingShl::overflowing_shl(v, 4),
346            (H::from(16u8), false)
347        );
348        // Amount == width overflows; masked to 0 → shift by 0.
349        assert_eq!(OverflowingShl::overflowing_shl(v, 32), (v, true));
350        assert_eq!(WrappingShl::wrapping_shl(v, 32), v);
351        assert_eq!(CheckedShl::checked_shl(v, 32), None);
352        assert_eq!(CheckedShl::checked_shl(v, 5), Some(H::from(32u8)));
353        assert_eq!(CheckedShr::checked_shr(v, 32), None);
354    }
355
356    #[test]
357    fn unbounded_saturates_to_zero() {
358        let v = H::from(0xFFu8).widened(4);
359        assert_eq!(UnboundedShl::unbounded_shl(v, 100), H::from(0u8));
360        assert_eq!(UnboundedShr::unbounded_shr(v, 100), H::from(0u8));
361        // Over-width shift keeps the operand width.
362        assert_eq!(UnboundedShl::unbounded_shl(v, 100).len(), 4);
363    }
364
365    #[test]
366    fn exact_shifts() {
367        let v = H::from(0b100u8).widened(4);
368        // 0b100 has 2 trailing zeros: shr by 2 is exact, by 3 loses the bit.
369        assert_eq!(ShrExact::shr_exact(v, 2), Some(H::from(1u8)));
370        assert_eq!(ShrExact::shr_exact(v, 3), None);
371        // shl by more than leading_zeros drops the top bit.
372        assert!(ShlExact::shl_exact(H::from(1u8).widened(4), 31).is_some());
373        assert_eq!(ShlExact::shl_exact(H::from(1u8).widened(4), 32), None);
374    }
375
376    // A whole-limb exact shr must keep the operand width so it's reversible.
377    #[test]
378    fn shr_exact_preserves_width() {
379        let v = H::from(256u16).widened(4); // [0, 1, 0, 0], len 4
380        let r = ShrExact::shr_exact(v, 8).unwrap();
381        assert_eq!(r, H::from(1u8));
382        assert_eq!(r.len(), 4, "exact shr must not narrow away the width");
383        // Reversible: shifting back left recovers the input.
384        assert_eq!(r << 8usize, H::from(256u16));
385    }
386
387    // Over-width `Shl<u32>` / `Shr<u32>` zero out without truncating the count
388    // (the 16-bit-usize hazard); Shl keeps the width, Shr empties.
389    #[test]
390    fn u32_operator_shifts_handle_over_width() {
391        let v = H::from(0xFFu8).widened(4);
392        let sl = core::ops::Shl::<u32>::shl(v, 100);
393        assert!(<H as const_num_traits::Zero>::is_zero(&sl));
394        assert_eq!(sl.len(), 4);
395        let sr = core::ops::Shr::<u32>::shr(v, 100);
396        assert!(<H as const_num_traits::Zero>::is_zero(&sr));
397    }
398
399    // funnel with n == 0 is a no-op even on a len-0 operand (value_bits() == 0
400    // would otherwise trip the `n < bits` assert).
401    #[test]
402    fn funnel_zero_shift_on_empty_operand() {
403        let z0 = H::new_zero_with_len(0);
404        assert_eq!(FunnelShl::funnel_shl(z0, z0, 0).len(), 0);
405        assert_eq!(FunnelShr::funnel_shr(z0, z0, 0).len(), 0);
406    }
407
408    #[test]
409    #[should_panic(expected = "must share a width")]
410    fn funnel_rejects_width_mismatch() {
411        let narrow = H::from(1u8); // len 1
412        let wide = H::from(1u8).widened(4); // len 4
413        FunnelShl::funnel_shl(narrow, wide, 1);
414    }
415
416    #[test]
417    fn funnel() {
418        // (hi=0x1234, lo=0x5678) as a 32-bit pair, funnel_shl by 8 →
419        // top 32 bits of (0x1234_5678 << 8) = 0x3456_78__ >> ... check value.
420        let hi = H::from(0x1234_5678u32);
421        let lo = H::from(0x9ABC_DEF0u32);
422        // funnel_shl by 8: (hi << 8) | (lo >> 24) = 0x3456_7800 | 0x9A = 0x3456_789A
423        assert_eq!(FunnelShl::funnel_shl(hi, lo, 8), H::from(0x3456_789Au32));
424        // funnel_shr by 8: (lo >> 8) | (hi << 24) = 0x009A_BCDE | 0x7800_0000 = 0x789A_BCDE
425        assert_eq!(FunnelShr::funnel_shr(hi, lo, 8), H::from(0x789A_BCDEu32));
426        assert_eq!(FunnelShl::funnel_shl(hi, lo, 0), hi);
427        assert_eq!(FunnelShr::funnel_shr(hi, lo, 0), lo);
428    }
429
430    // The `&Self` mirrors agree with the value impls.
431    #[test]
432    fn by_ref_matches_value() {
433        let v = H::from(1u8).widened(4);
434        assert_eq!(
435            OverflowingShl::overflowing_shl(&v, 4),
436            OverflowingShl::overflowing_shl(v, 4)
437        );
438        assert_eq!(
439            WrappingShl::wrapping_shl(&v, 5),
440            WrappingShl::wrapping_shl(v, 5)
441        );
442        assert_eq!(
443            CheckedShl::checked_shl(&v, 5),
444            CheckedShl::checked_shl(v, 5)
445        );
446        assert_eq!(
447            UnboundedShl::unbounded_shl(&v, 100),
448            UnboundedShl::unbounded_shl(v, 100)
449        );
450        assert_eq!(ShlExact::shl_exact(&v, 3), ShlExact::shl_exact(v, 3));
451        assert_eq!(ShrExact::shr_exact(&v, 0), ShrExact::shr_exact(v, 0));
452
453        let hi = H::from(0x1234_5678u32);
454        let lo = H::from(0x9ABC_DEF0u32);
455        assert_eq!(
456            FunnelShl::funnel_shl(&hi, &lo, 8),
457            FunnelShl::funnel_shl(hi, lo, 8)
458        );
459        assert_eq!(
460            FunnelShr::funnel_shr(&hi, &lo, 8),
461            FunnelShr::funnel_shr(hi, lo, 8)
462        );
463    }
464}