use super::div::divrem;
use super::int::BigInt;
use super::uint::BigUint;
use oxinum_core::Sign;
pub fn gcd_extended(a: &BigUint, b: &BigUint) -> (BigUint, BigInt, BigInt) {
if b.is_zero() {
return (a.clone(), BigInt::from(1i64), BigInt::from(0i64));
}
if a.is_zero() {
return (b.clone(), BigInt::from(0i64), BigInt::from(1i64));
}
ext_gcd_classical(a.clone(), b.clone())
}
fn ext_gcd_classical(a: BigUint, b: BigUint) -> (BigUint, BigInt, BigInt) {
let mut old_r: BigUint = a;
let mut r: BigUint = b;
let mut old_s: BigInt = BigInt::from(1i64);
let mut s: BigInt = BigInt::from(0i64);
let mut old_t: BigInt = BigInt::from(0i64);
let mut t: BigInt = BigInt::from(1i64);
while !r.is_zero() {
let (q, rem) = divrem(&old_r, &r);
let q_int = BigInt::from(q);
let new_r = rem;
let new_s = &old_s - &q_int * &s;
let new_t = &old_t - &q_int * &t;
old_r = r;
r = new_r;
old_s = s;
s = new_s;
old_t = t;
t = new_t;
}
(old_r, old_s, old_t)
}
pub fn mod_inv(a: &BigUint, m: &BigUint) -> Option<BigUint> {
if m.is_zero() {
return None;
}
let (g, x, _y) = gcd_extended(a, m);
if !g.is_one() {
return None;
}
let m_int = BigInt::from(m.clone());
let x_mod = &x % &m_int; let x_positive = if x_mod.is_negative() {
&x_mod + &m_int
} else {
x_mod
};
let (sign, mag) = x_positive.into_parts();
debug_assert!(
sign == Sign::Positive || mag.is_zero(),
"mod_inv: reduced coefficient should be non-negative"
);
Some(mag)
}
#[cfg(test)]
mod tests {
use super::*;
fn bu(n: u64) -> BigUint {
BigUint::from_u64(n)
}
#[test]
fn ext_gcd_zero_a() {
let (g, x, y) = gcd_extended(&bu(0), &bu(7));
assert_eq!(g, bu(7));
let lhs = BigInt::from(0i64) * x + BigInt::from(7i64) * y;
assert_eq!(lhs, BigInt::from(7i64));
}
#[test]
fn ext_gcd_zero_b() {
let (g, x, y) = gcd_extended(&bu(5), &bu(0));
assert_eq!(g, bu(5));
let lhs = BigInt::from(5i64) * x + BigInt::from(0i64) * y;
assert_eq!(lhs, BigInt::from(5i64));
}
#[test]
fn ext_gcd_both_zero() {
let (g, x, y) = gcd_extended(&bu(0), &bu(0));
assert_eq!(g, bu(0));
let _ = (x, y); }
#[test]
fn ext_gcd_simple_12_8() {
let (g, x, y) = gcd_extended(&bu(12), &bu(8));
assert_eq!(g, bu(4));
let sum = BigInt::from(12i64) * x + BigInt::from(8i64) * y;
assert_eq!(sum, BigInt::from(4i64));
}
#[test]
fn ext_gcd_coprime_35_15() {
let (g, x, y) = gcd_extended(&bu(35), &bu(15));
assert_eq!(g, bu(5));
let sum = BigInt::from(35i64) * x + BigInt::from(15i64) * y;
assert_eq!(sum, BigInt::from(5i64));
}
#[test]
fn ext_gcd_identical() {
let (g, x, y) = gcd_extended(&bu(42), &bu(42));
assert_eq!(g, bu(42));
let sum = BigInt::from(42i64) * x + BigInt::from(42i64) * y;
assert_eq!(sum, BigInt::from(42i64));
}
#[test]
fn ext_gcd_one_larger() {
let (g, x, y) = gcd_extended(&bu(100), &bu(1));
assert_eq!(g, bu(1));
let sum = BigInt::from(100i64) * x + BigInt::from(1i64) * y;
assert_eq!(sum, BigInt::from(1i64));
}
#[test]
fn mod_inv_basic() {
assert_eq!(mod_inv(&bu(3), &bu(7)), Some(bu(5)));
}
#[test]
fn mod_inv_no_inverse() {
assert_eq!(mod_inv(&bu(6), &bu(9)), None);
}
#[test]
fn mod_inv_zero_modulus() {
assert_eq!(mod_inv(&bu(3), &bu(0)), None);
}
#[test]
fn mod_inv_result_in_range() {
for m in [7u64, 13, 101, 65537] {
for a in 1u64..m.min(20) {
if let Some(inv) = mod_inv(&bu(a), &bu(m)) {
assert!(inv < bu(m), "mod_inv result >= m for a={a}, m={m}");
}
}
}
}
}