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}