|
4 | 4 | use crate::tables::{CUBIC_MODULI, CUBIC_RESIDUAL, QUAD_MODULI, QUAD_RESIDUAL}; |
5 | 5 | use crate::traits::{BitTest, ExactRoots}; |
6 | 6 |
|
| 7 | +use num_integer::Roots; |
| 8 | + |
7 | 9 | #[cfg(feature = "num-bigint")] |
8 | 10 | use num_bigint::{BigInt, BigUint, ToBigInt}; |
9 | 11 | #[cfg(feature = "num-bigint")] |
@@ -49,6 +51,18 @@ impl BitTest for BigUint { |
49 | 51 | macro_rules! impl_exactroot_prim { |
50 | 52 | ($($T:ty)*) => {$( |
51 | 53 | impl ExactRoots for $T { |
| 54 | + fn nth_root_exact(&self, n: u32) -> Option<Self> { |
| 55 | + // For even roots of negative numbers, return None instead of panicking |
| 56 | + if self < &0 && n % 2 == 0 { |
| 57 | + return None; |
| 58 | + } |
| 59 | + let r = self.nth_root(n); |
| 60 | + if &r.clone().pow(n) == self { |
| 61 | + Some(r) |
| 62 | + } else { |
| 63 | + None |
| 64 | + } |
| 65 | + } |
52 | 66 | fn sqrt_exact(&self) -> Option<Self> { |
53 | 67 | if self < &0 { return None; } |
54 | 68 | let shift = self.trailing_zeros(); |
@@ -140,6 +154,25 @@ impl ExactRoots for BigUint { |
140 | 154 |
|
141 | 155 | #[cfg(feature = "num-bigint")] |
142 | 156 | impl ExactRoots for BigInt { |
| 157 | + fn nth_root_exact(&self, n: u32) -> Option<Self> { |
| 158 | + // For even roots of negative numbers, return None instead of panicking |
| 159 | + if self.is_negative() && n % 2 == 0 { |
| 160 | + return None; |
| 161 | + } |
| 162 | + |
| 163 | + // For odd roots, handle negative numbers by taking the root of the magnitude |
| 164 | + // and then applying the sign |
| 165 | + if self.is_negative() { |
| 166 | + self.magnitude() |
| 167 | + .nth_root_exact(n) |
| 168 | + .and_then(|u| u.to_bigint()) |
| 169 | + .map(|v| -v) |
| 170 | + } else { |
| 171 | + self.magnitude() |
| 172 | + .nth_root_exact(n) |
| 173 | + .and_then(|u| u.to_bigint()) |
| 174 | + } |
| 175 | + } |
143 | 176 | fn sqrt_exact(&self) -> Option<Self> { |
144 | 177 | self.to_biguint() |
145 | 178 | .and_then(|u| u.sqrt_exact()) |
@@ -246,4 +279,79 @@ mod tests { |
246 | 279 | } |
247 | 280 | } |
248 | 281 | } |
| 282 | + |
| 283 | + #[test] |
| 284 | + fn test_nth_root_exact_negative_even_root() { |
| 285 | + // Test for issue #25: nth_root_exact(2) should return None for negative numbers |
| 286 | + // instead of panicking |
| 287 | + let result = (-1i32).nth_root_exact(2); |
| 288 | + assert!( |
| 289 | + result.is_none(), |
| 290 | + "nth_root_exact(2) should return None for negative numbers" |
| 291 | + ); |
| 292 | + |
| 293 | + // Test other negative numbers with even roots |
| 294 | + let result = (-4i32).nth_root_exact(2); |
| 295 | + assert!( |
| 296 | + result.is_none(), |
| 297 | + "nth_root_exact(2) should return None for negative numbers" |
| 298 | + ); |
| 299 | + |
| 300 | + let result = (-8i32).nth_root_exact(4); |
| 301 | + assert!( |
| 302 | + result.is_none(), |
| 303 | + "nth_root_exact(4) should return None for negative numbers" |
| 304 | + ); |
| 305 | + |
| 306 | + // Test that odd roots of negative numbers still work |
| 307 | + let result = (-8i32).nth_root_exact(3); |
| 308 | + assert_eq!( |
| 309 | + result, |
| 310 | + Some(-2), |
| 311 | + "nth_root_exact(3) should work for negative numbers" |
| 312 | + ); |
| 313 | + |
| 314 | + let result = (-27i32).nth_root_exact(3); |
| 315 | + assert_eq!( |
| 316 | + result, |
| 317 | + Some(-3), |
| 318 | + "nth_root_exact(3) should work for negative numbers" |
| 319 | + ); |
| 320 | + } |
| 321 | + |
| 322 | + #[test] |
| 323 | + fn test_nth_root_exact_all_signed_types() { |
| 324 | + // Test all signed integer types with even roots of negative numbers |
| 325 | + assert_eq!((-1i8).nth_root_exact(2), None); |
| 326 | + assert_eq!((-1i16).nth_root_exact(2), None); |
| 327 | + assert_eq!((-1i32).nth_root_exact(2), None); |
| 328 | + assert_eq!((-1i64).nth_root_exact(2), None); |
| 329 | + assert_eq!((-1i128).nth_root_exact(2), None); |
| 330 | + assert_eq!((-1isize).nth_root_exact(2), None); |
| 331 | + |
| 332 | + // Test odd roots work correctly |
| 333 | + assert_eq!((-8i32).nth_root_exact(3), Some(-2)); |
| 334 | + assert_eq!((-32i32).nth_root_exact(5), Some(-2)); |
| 335 | + |
| 336 | + // Test positive cases still work |
| 337 | + assert_eq!(16i32.nth_root_exact(4), Some(2)); |
| 338 | + assert_eq!(32i32.nth_root_exact(5), Some(2)); |
| 339 | + } |
| 340 | + |
| 341 | + #[test] |
| 342 | + #[cfg(feature = "num-bigint")] |
| 343 | + fn test_nth_root_exact_bigint_negative() { |
| 344 | + use num_bigint::BigInt; |
| 345 | + |
| 346 | + // Test even roots return None for negative BigInt |
| 347 | + assert_eq!(BigInt::from(-1).nth_root_exact(2), None); |
| 348 | + assert_eq!(BigInt::from(-16).nth_root_exact(4), None); |
| 349 | + |
| 350 | + // Test odd roots work for negative BigInt |
| 351 | + assert_eq!(BigInt::from(-8).nth_root_exact(3), Some(BigInt::from(-2))); |
| 352 | + assert_eq!( |
| 353 | + BigInt::from(-1000000000i64).nth_root_exact(3), |
| 354 | + Some(BigInt::from(-1000i32)) |
| 355 | + ); |
| 356 | + } |
249 | 357 | } |
0 commit comments