Skip to main content

oxinum_int/native/
roots.rs

1//! Integer square root and generalized integer nth root (Newton's method).
2//!
3//! All routines return the FLOOR of the true root. After Newton iteration
4//! converges, a final correction step verifies the floor invariant
5//! `x^n <= value < (x+1)^n` and, if necessary, decrements `x` by one to
6//! restore it. This guards against the edge cases near perfect-power
7//! boundaries that pure Newton can miss by one.
8
9use super::int::BigInt;
10use super::uint::BigUint;
11use crate::{OxiNumError, OxiNumResult};
12use oxinum_core::Sign;
13
14impl BigUint {
15    /// Integer square root (floor) via Newton's method.
16    ///
17    /// Returns the largest `x` such that `x*x <= self`.
18    ///
19    /// # Examples
20    ///
21    /// ```
22    /// use oxinum_int::native::BigUint;
23    /// assert_eq!(BigUint::from_u64(16).sqrt(), BigUint::from_u64(4));
24    /// assert_eq!(BigUint::from_u64(17).sqrt(), BigUint::from_u64(4));
25    /// assert_eq!(BigUint::zero().sqrt(), BigUint::zero());
26    /// assert_eq!(BigUint::from_u64(1).sqrt(), BigUint::from_u64(1));
27    /// ```
28    pub fn sqrt(&self) -> BigUint {
29        if self.is_zero() {
30            return BigUint::zero();
31        }
32        if self.is_one() {
33            return BigUint::one();
34        }
35        // Initial estimate: x0 = 1 << ceil(bit_length / 2). This guarantees
36        // x0^2 >= self (so the Newton iteration is monotonically decreasing).
37        let bl = self.bit_length();
38        let init_shift = bl.div_ceil(2);
39        let mut x = BigUint::one().shl_bits(init_shift);
40        loop {
41            // x_next = (x + self / x) / 2
42            let q = self / &x;
43            let sum = &x + &q;
44            let next = sum.shr_bits(1);
45            if next >= x {
46                break;
47            }
48            x = next;
49        }
50        // Floor correction: ensure x*x <= self < (x+1)*(x+1). The Newton
51        // step above can leave x one too large in rare boundary cases.
52        debug_assert!(
53            &x * &x <= *self || x.is_zero(),
54            "sqrt floor invariant lower"
55        );
56        while &x * &x > *self {
57            // Defensive: should never iterate after Newton convergence, but
58            // keep the loop to make the invariant unconditional.
59            x = x
60                .checked_sub(&BigUint::one())
61                .expect("x > 0 because x*x > self > 0");
62        }
63        debug_assert!({
64            let xp1 = &x + &BigUint::one();
65            &xp1 * &xp1 > *self
66        });
67        x
68    }
69
70    /// Integer `n`-th root (floor) via Newton's method generalized.
71    ///
72    /// Returns the largest `x` such that `x^n <= self`. `n` must be `>= 1`.
73    ///
74    /// # Errors
75    ///
76    /// Returns [`OxiNumError::Precision`] if `n == 0`.
77    ///
78    /// # Examples
79    ///
80    /// ```
81    /// use oxinum_int::native::BigUint;
82    /// assert_eq!(BigUint::from_u64(27).nth_root(3).unwrap(), BigUint::from_u64(3));
83    /// assert_eq!(BigUint::from_u64(28).nth_root(3).unwrap(), BigUint::from_u64(3));
84    /// assert_eq!(BigUint::from_u64(81).nth_root(4).unwrap(), BigUint::from_u64(3));
85    /// ```
86    pub fn nth_root(&self, n: u32) -> OxiNumResult<BigUint> {
87        if n == 0 {
88            return Err(OxiNumError::Precision("zeroth root is undefined".into()));
89        }
90        if n == 1 {
91            return Ok(self.clone());
92        }
93        if n == 2 {
94            return Ok(self.sqrt());
95        }
96        if self.is_zero() {
97            return Ok(BigUint::zero());
98        }
99        if self.is_one() {
100            return Ok(BigUint::one());
101        }
102        // Initial estimate: x0 = 1 << ceil(bit_length / n). For n >= 2 and
103        // value > 1, this gives x0 >= true root (Newton converges
104        // monotonically downward).
105        let bl = self.bit_length();
106        let n64 = n as u64;
107        let init_shift = bl.div_ceil(n64);
108        let mut x = BigUint::one().shl_bits(init_shift);
109        let n_big = BigUint::from_u64(n64);
110        let nm1 = n - 1;
111        let nm1_big = BigUint::from_u64((n - 1) as u64);
112        // Newton: x_next = ((n-1) * x + value / x^(n-1)) / n
113        loop {
114            let xnm1 = x.pow(nm1);
115            let q = self / &xnm1;
116            let lhs = &nm1_big * &x;
117            let sum = &lhs + &q;
118            let next = &sum / &n_big;
119            if next >= x {
120                break;
121            }
122            x = next;
123        }
124        // Floor correction: ensure x^n <= self.
125        while x.pow(n) > *self {
126            x = x
127                .checked_sub(&BigUint::one())
128                .expect("x > 0 because x^n > self > 0");
129        }
130        // Defensive (debug-only): confirm x^n <= self < (x+1)^n.
131        debug_assert!(x.pow(n) <= *self);
132        debug_assert!({
133            let xp1 = &x + &BigUint::one();
134            xp1.pow(n) > *self
135        });
136        Ok(x)
137    }
138}
139
140impl BigInt {
141    /// Integer `n`-th root for signed values.
142    ///
143    /// - `n == 0`: error ([`OxiNumError::Precision`]).
144    /// - Negative self with even `n`: error (no real even root of a negative
145    ///   integer).
146    /// - Negative self with odd `n`: returns the unique negative root.
147    /// - Otherwise: floor of the positive real root.
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// use oxinum_int::native::BigInt;
153    /// assert_eq!(BigInt::from(-8i64).nth_root(3).unwrap(), BigInt::from(-2i64));
154    /// assert_eq!(BigInt::from(27i64).nth_root(3).unwrap(), BigInt::from(3i64));
155    /// assert!(BigInt::from(-4i64).nth_root(2).is_err());
156    /// assert!(BigInt::from(10i64).nth_root(0).is_err());
157    /// ```
158    pub fn nth_root(&self, n: u32) -> OxiNumResult<BigInt> {
159        if n == 0 {
160            return Err(OxiNumError::Precision("zeroth root is undefined".into()));
161        }
162        if self.sign() == Sign::Negative && !self.magnitude().is_zero() {
163            if n % 2 == 0 {
164                return Err(OxiNumError::Precision(
165                    format!("even ({n}-th) root of a negative integer is not a real number").into(),
166                ));
167            }
168            let root_mag = self.magnitude().nth_root(n)?;
169            return Ok(BigInt::from_parts(Sign::Negative, root_mag));
170        }
171        // Positive (or zero) path.
172        let root_mag = self.magnitude().nth_root(n)?;
173        Ok(BigInt::from_parts(Sign::Positive, root_mag))
174    }
175
176    /// Integer square root for signed values. Errors for negative inputs.
177    ///
178    /// # Examples
179    ///
180    /// ```
181    /// use oxinum_int::native::BigInt;
182    /// assert_eq!(BigInt::from(49i64).sqrt().unwrap(), BigInt::from(7i64));
183    /// assert!(BigInt::from(-1i64).sqrt().is_err());
184    /// ```
185    pub fn sqrt(&self) -> OxiNumResult<BigInt> {
186        self.nth_root(2)
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn sqrt_perfect_squares() {
196        for k in 0u64..40 {
197            let n = BigUint::from_u64(k * k);
198            assert_eq!(n.sqrt(), BigUint::from_u64(k));
199        }
200    }
201
202    #[test]
203    fn sqrt_non_perfect() {
204        // 17 -> 4 (4^2 = 16, 5^2 = 25)
205        assert_eq!(BigUint::from_u64(17).sqrt(), BigUint::from_u64(4));
206        assert_eq!(BigUint::from_u64(99).sqrt(), BigUint::from_u64(9));
207    }
208
209    #[test]
210    fn nth_root_basics() {
211        // cube root
212        assert_eq!(
213            BigUint::from_u64(27).nth_root(3).expect("ok"),
214            BigUint::from_u64(3)
215        );
216        assert_eq!(
217            BigUint::from_u64(28).nth_root(3).expect("ok"),
218            BigUint::from_u64(3)
219        );
220        // 4th root of 16 = 2
221        assert_eq!(
222            BigUint::from_u64(16).nth_root(4).expect("ok"),
223            BigUint::from_u64(2)
224        );
225    }
226
227    #[test]
228    fn nth_root_signed_negative_odd() {
229        assert_eq!(
230            BigInt::from(-8i64).nth_root(3).expect("ok"),
231            BigInt::from(-2i64)
232        );
233        assert_eq!(
234            BigInt::from(-1000i64).nth_root(3).expect("ok"),
235            BigInt::from(-10i64)
236        );
237    }
238
239    #[test]
240    fn nth_root_signed_negative_even_errors() {
241        assert!(BigInt::from(-4i64).nth_root(2).is_err());
242        assert!(BigInt::from(-1024i64).nth_root(4).is_err());
243    }
244
245    #[test]
246    fn nth_root_zero_argument_errors() {
247        assert!(BigUint::from_u64(10).nth_root(0).is_err());
248        assert!(BigInt::from(10i64).nth_root(0).is_err());
249    }
250}