Skip to main content

fixed_bigint/heapless/
roots_impl.rs

1//! `num_integer::Roots` (nth_root, and the `sqrt`/`cbrt` defaults) for
2//! `HeaplessBigInt<_, Nct>`.
3//!
4//! Newton's method, Nct-only (value-dependent convergence). The estimate
5//! `x` and the constants `n`, `n-1` are all pinned to the operand width so
6//! every `pow`/`*`/`/` resolves at the value width. Power evaluations go
7//! through `checked_pow`: an over-width `x^k` would panic (Nct multiply) or
8//! silently wrap, either of which breaks the clamp loops, so overflow is
9//! treated as "arbitrarily large" (quotient 0 / probe fails) instead.
10
11use super::HeaplessBigInt;
12use crate::MachineWord;
13use const_num_traits::{CarryingMul, CheckedPow, Nct};
14use num_integer::Roots;
15use num_traits::{FromPrimitive, One, Zero};
16
17impl<T, const CAP: usize> Roots for HeaplessBigInt<T, CAP, Nct>
18where
19    T: MachineWord + CarryingMul<Unsigned = T, Output = T>,
20{
21    fn nth_root(&self, n: u32) -> Self {
22        if n == 0 {
23            panic!("nth_root: n must be non-zero");
24        }
25        // Zero and one are their own roots and already carry the width.
26        if Zero::is_zero(self) {
27            return *self;
28        }
29        if One::is_one(self) || n == 1 {
30            return *self;
31        }
32
33        let width = self.len();
34        let bit_len = self.bit_length();
35        // Root of a value with fewer than `n` bits is 1.
36        if n as usize > bit_len {
37            return <Self as One>::one().widened(width);
38        }
39
40        let one_w = <Self as One>::one().widened(width);
41        let initial_exp = (bit_len as u32).div_ceil(n).max(1);
42        let mut x = one_w << (initial_exp as usize);
43
44        // `n` fits the operand width (n <= bit_len <= width·word_bits), so
45        // build the constants value-based at that width — `From::<u32>` would
46        // carry the 4-byte source width (wrong shape, and it panics when
47        // CAP < 4/word).
48        let n_val = width_const(n, width);
49        let n_minus_1 = width_const(n - 1, width);
50
51        loop {
52            // x^(n-1) overflowing the width means it dwarfs `self`, so the
53            // quotient `self / x^(n-1)` is 0.
54            let quotient = match CheckedPow::checked_pow(x, n - 1) {
55                Some(p) if Zero::is_zero(&p) => break,
56                Some(p) => *self / p,
57                None => Self::new_zero_with_len(width),
58            };
59            let numerator = x * n_minus_1 + quotient;
60            let x_new = numerator / n_val;
61            if x_new >= x {
62                break;
63            }
64            x = x_new;
65        }
66
67        // Clamp to r^n <= self < (r+1)^n. An over-width x^n is > self (self
68        // fits the width), so `None` reads as "greater".
69        while CheckedPow::checked_pow(x, n).is_none_or(|p| p > *self) {
70            x -= one_w;
71        }
72        let mut x_plus_one = x + one_w;
73        while CheckedPow::checked_pow(x_plus_one, n).is_some_and(|p| p <= *self) {
74            x += one_w;
75            x_plus_one = x + one_w;
76        }
77
78        x
79    }
80}
81
82/// Build a small `u32` value at exactly `width` limbs. Used for the Newton
83/// constants, which must carry the operand width, not the `u32` source width.
84/// The caller guarantees the value fits `width` limbs (`n <= width·word_bits`).
85fn width_const<T, const CAP: usize>(value: u32, width: u16) -> HeaplessBigInt<T, CAP, Nct>
86where
87    T: MachineWord,
88{
89    // `from_u64` is value-based (natural width, never panics); widen to the
90    // operand width. natural_len <= width because the value fits `width` limbs.
91    <HeaplessBigInt<T, CAP, Nct> as FromPrimitive>::from_u64(value as u64)
92        .expect("width_const: value fits the carrier")
93        .widened(width)
94}
95
96#[cfg(test)]
97mod tests {
98    use super::HeaplessBigInt;
99    use num_integer::Roots;
100
101    type H = HeaplessBigInt<u8, 8>;
102
103    #[test]
104    fn sqrt_cbrt_nth() {
105        assert_eq!(H::from(16u8).sqrt(), H::from(4u8));
106        assert_eq!(H::from(15u8).sqrt(), H::from(3u8));
107        assert_eq!(H::from(27u8).cbrt(), H::from(3u8));
108        assert_eq!(H::from(63u8).cbrt(), H::from(3u8));
109        assert_eq!(H::from(81u8).nth_root(4), H::from(3u8));
110        assert_eq!(H::from(42u8).nth_root(1), H::from(42u8));
111        // A value with fewer than n bits roots to 1.
112        assert_eq!(H::from(2u8).nth_root(100), H::from(1u8));
113    }
114
115    #[test]
116    #[should_panic(expected = "nth_root: n must be non-zero")]
117    fn nth_root_zero_n_panics() {
118        H::from(16u8).nth_root(0);
119    }
120
121    // Newton's estimate is seeded at the operand width; the root carries it.
122    #[test]
123    fn nth_root_preserves_width() {
124        let n = H::from(10000u16).widened(8);
125        let r = n.sqrt();
126        assert_eq!(r, H::from(100u8));
127        assert_eq!(r.len(), 8);
128    }
129
130    #[test]
131    fn root_correctness_small_range() {
132        for x in 1..=200u16 {
133            let xi = H::from(x);
134            let s = xi.sqrt();
135            assert!(
136                s.pow(2) <= xi && (s + H::from(1u8)).pow(2) > xi,
137                "sqrt({x})"
138            );
139            let c = xi.cbrt();
140            assert!(
141                c.pow(3) <= xi && (c + H::from(1u8)).pow(3) > xi,
142                "cbrt({x})"
143            );
144        }
145    }
146
147    // sqrt of the max value at the exact operand width: the upper probe
148    // (x+1)^2 = 2^32 overflows the 32-bit width, which must read as ">self"
149    // via checked_pow rather than panicking on the Nct multiply.
150    #[test]
151    fn sqrt_of_max_at_exact_width_does_not_panic() {
152        type H1 = HeaplessBigInt<u32, 1>; // exactly 32-bit width
153        assert_eq!(H1::from(u32::MAX).sqrt(), H1::from(0xFFFFu32));
154    }
155
156    // Narrow word + tiny CAP: the Newton constants must not go through the
157    // 4-byte u32 `From` (which needs 4 limbs and would panic at CAP 1).
158    #[test]
159    fn sqrt_narrow_word_tiny_cap() {
160        type H1 = HeaplessBigInt<u8, 1>; // 8-bit numbers, CAP 1
161        assert_eq!(H1::from(196u8).sqrt(), H1::from(14u8));
162        assert_eq!(H1::from(255u8).sqrt(), H1::from(15u8));
163        assert_eq!(H1::from(196u8).sqrt().len(), 1);
164    }
165}