Skip to main content

fixed_bigint/heapless/
num_traits_bridge.rs

1//! `num_traits` bridge for `HeaplessBigInt` (feature = "num-traits").
2//!
3//! Thin forwards onto the const-num-traits identity/bounds impls plus
4//! byte-based primitive extraction, mirroring `FixedUInt`'s bridge so a
5//! `HeaplessBigInt` satisfies the classic `num_traits` bounds
6//! (`Zero`/`One`/`Bounded`/`NumCast`/`ToPrimitive`/`FromPrimitive`). All
7//! are personality-generic — no `Nct` gate, matching `FixedUInt`.
8
9use super::HeaplessBigInt;
10use crate::MachineWord;
11use const_num_traits::{Bounded, CarryingMul, ConstOne, ConstZero, Nct, Personality, Zero};
12
13impl<T: MachineWord, const CAP: usize, P: Personality> num_traits::Zero
14    for HeaplessBigInt<T, CAP, P>
15{
16    fn zero() -> Self {
17        <Self as ConstZero>::ZERO
18    }
19
20    fn is_zero(&self) -> bool {
21        <Self as Zero>::is_zero(self)
22    }
23}
24
25// `num_traits::One: Mul<Self>`, and heapless multiplication needs
26// `CarryingMul` on the word (unlike `FixedUInt`, which widens via
27// `DoubleWord`), so this impl carries the same bound its `Mul` does.
28impl<T: MachineWord + CarryingMul<Unsigned = T, Output = T>, const CAP: usize, P: Personality>
29    num_traits::One for HeaplessBigInt<T, CAP, P>
30{
31    fn one() -> Self {
32        <Self as ConstOne>::ONE
33    }
34}
35
36impl<T: MachineWord, const CAP: usize, P: Personality> num_traits::Bounded
37    for HeaplessBigInt<T, CAP, P>
38{
39    fn min_value() -> Self {
40        <Self as Bounded>::min_value()
41    }
42
43    fn max_value() -> Self {
44        <Self as Bounded>::max_value()
45    }
46}
47
48impl<T: MachineWord, const CAP: usize, P: Personality> num_traits::ToPrimitive
49    for HeaplessBigInt<T, CAP, P>
50{
51    fn to_i64(&self) -> Option<i64> {
52        // Unsigned carrier: mirror `FixedUInt`, which never claims `i64`.
53        None
54    }
55
56    fn to_u64(&self) -> Option<u64> {
57        let word_size = core::mem::size_of::<T>();
58        let len = self.len as usize;
59        // Words that can reach the low 64 bits.
60        let iter_limit = core::cmp::min(8 / word_size, len);
61        // Anything set above bit 63 overflows a u64. Limbs beyond `len` are
62        // zero by the zero-tail invariant, so scanning `iter_limit..len`
63        // is enough.
64        for i in iter_limit..len {
65            if !super::is_zero(&self.limbs[i]) {
66                return None;
67            }
68        }
69        let mut ret: u64 = 0;
70        for (i, word) in self.limbs.iter().take(iter_limit).enumerate() {
71            let bytes = word.to_le_bytes();
72            for (j, &byte) in bytes.as_ref().iter().enumerate() {
73                let bit_shift = (i * word_size + j) * 8;
74                if bit_shift < 64 {
75                    ret |= (byte as u64) << bit_shift;
76                }
77            }
78        }
79        Some(ret)
80    }
81}
82
83impl<T: MachineWord, const CAP: usize, P: Personality> num_traits::FromPrimitive
84    for HeaplessBigInt<T, CAP, P>
85{
86    fn from_i64(_: i64) -> Option<Self> {
87        None
88    }
89
90    fn from_u64(input: u64) -> Option<Self> {
91        // Reject values the carrier can't hold. When `max` itself overflows
92        // a u64 the carrier is >= 64 bits wide, so every u64 fits.
93        if let Some(max) =
94            num_traits::ToPrimitive::to_u64(&<Self as num_traits::Bounded>::max_value())
95        {
96            if input > max {
97                return None;
98            }
99        }
100        // Construct at natural width: trim trailing zero bytes so a small
101        // value in a small-`CAP` carrier stays in bounds. (Unlike `From`,
102        // which asserts on oversize — the trait contract here is to return
103        // `None`, never panic, and the size check above already did that.)
104        let bytes = input.to_le_bytes();
105        let mut sig = bytes.len();
106        while sig > 0 && bytes[sig - 1] == 0 {
107            sig -= 1;
108        }
109        Some(Self::from_le_bytes(&bytes[..sig]))
110    }
111}
112
113impl<T: MachineWord, const CAP: usize, P: Personality> num_traits::NumCast
114    for HeaplessBigInt<T, CAP, P>
115{
116    fn from<X: num_traits::ToPrimitive>(arg: X) -> Option<Self> {
117        <Self as num_traits::FromPrimitive>::from_u64(arg.to_u64()?)
118    }
119}
120
121// Marker: an unsigned `Num`. Nct-only, since `Num` (via `from_str_radix`) is.
122impl<T, const CAP: usize> num_traits::Unsigned for HeaplessBigInt<T, CAP, Nct> where
123    T: MachineWord + CarryingMul<Unsigned = T, Output = T>
124{
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use num_traits::{FromPrimitive, ToPrimitive};
131
132    type H32x8 = HeaplessBigInt<u32, 8>; // 256-bit carrier
133
134    // The 32-bit foundation surface (Zero/One/Bounded/NumCast, round-trip and
135    // overflow) is covered for both carriers in tests/carrier_num_traits.rs.
136    // These two exercise heapless behavior the fixed-width harness can't reach.
137
138    #[test]
139    fn from_u64_is_natural_width() {
140        // A small value in a wide carrier constructs at its own width, not CAP.
141        use const_num_traits::BitsPrecision;
142        let small = H32x8::from_u64(1).unwrap();
143        assert_eq!(small.bits_precision(), 32); // one u32 limb, not 8
144    }
145
146    #[test]
147    fn wide_value_roundtrip() {
148        // Values wider than 32 bits round-trip via multi-limb u64 assembly in
149        // a carrier wide enough to hold them.
150        for v in [0x1_0000_0000u64, 0x1234_5678_9ABC, 0xFFFF_FFFF_FFFF_FFFF] {
151            assert_eq!(H32x8::from_u64(v).unwrap().to_u64(), Some(v));
152        }
153        // The 256-bit max exceeds u64, so to_u64 saturates to None — the
154        // branch from_u64 relies on to accept every u64 above.
155        assert_eq!(<H32x8 as num_traits::Bounded>::max_value().to_u64(), None);
156    }
157}