use num_bigint::{BigUint, ToBigInt};
use num_traits::{One, Signed, Zero};
pub fn chakravala(d: &BigUint) -> Option<(BigUint, BigUint)> {
let d_sqrt = d.sqrt();
if &d_sqrt * &d_sqrt == *d {
return None; }
let mut a = d_sqrt.clone();
let mut b = BigUint::one();
let d_bigint = d.to_bigint().unwrap();
let mut k = (&a * &a).to_bigint().unwrap() - &d_bigint;
if k.is_zero() {
return None;
}
while !k.is_one() {
let k_abs = k.abs();
let k_abs_biguint = k_abs.to_biguint().unwrap();
let b_inv = match b.modinv(&k_abs_biguint) {
Some(inv) => inv,
None => return None, };
let m_congruence = (&a * &b_inv).to_bigint().unwrap() % &k;
let m_neg = -m_congruence;
let mut m = m_neg.clone();
if m.is_negative() || m.is_zero() {
m += &k_abs;
}
let mut best_m = m.clone();
let mut min_diff = (&best_m * &best_m - &d_bigint).abs();
let search_limit = core::cmp::min(k_abs.bits() as usize + 5, 30);
for _ in 0..search_limit {
let next_m = &m + &k_abs;
let diff = (&next_m * &next_m - &d_bigint).abs();
if diff < min_diff {
min_diff = diff.clone();
best_m = next_m.clone();
m = next_m;
} else if diff > &min_diff * 2u32 {
break;
} else {
m = next_m;
}
}
let m = best_m;
let new_a = (&a.to_bigint().unwrap() * &m + &d_bigint * b.to_bigint().unwrap()) / &k_abs;
let new_b = (&a.to_bigint().unwrap() + b.to_bigint().unwrap() * &m) / &k_abs;
let new_k = (m.pow(2) - &d_bigint) / &k;
a = new_a.to_biguint().unwrap();
b = new_b.to_biguint().unwrap();
k = new_k;
}
Some((a, b))
}
#[cfg(test)]
mod tests {
use super::*;
use num_bigint::ToBigUint;
#[test]
fn test_chakravala_d_13() {
let d = 13.to_biguint().unwrap();
let (x, y) = chakravala(&d).unwrap();
assert_eq!(x, 649.to_biguint().unwrap());
assert_eq!(y, 180.to_biguint().unwrap());
}
#[test]
fn test_chakravala_d_61() {
let d = 61.to_biguint().unwrap();
let (x, y) = chakravala(&d).unwrap();
assert_eq!(x, 1766319049.to_biguint().unwrap());
assert_eq!(y, 226153980.to_biguint().unwrap());
}
#[test]
fn test_chakravala_d_67() {
let d = 67.to_biguint().unwrap();
let (x, y) = chakravala(&d).unwrap();
assert_eq!(x, 48842.to_biguint().unwrap());
assert_eq!(y, 5967.to_biguint().unwrap());
}
#[test]
fn test_chakravala_perfect_square() {
let d = 64.to_biguint().unwrap();
assert!(chakravala(&d).is_none());
}
#[test]
fn test_chakravala_d_2() {
let d = 2.to_biguint().unwrap();
let (x, y) = chakravala(&d).unwrap();
assert_eq!(x, 3.to_biguint().unwrap());
assert_eq!(y, 2.to_biguint().unwrap());
}
}