use super::div::divrem;
use super::int::BigInt;
use super::uint::BigUint;
use core::mem::swap;
const LEHMER_THRESHOLD_LIMBS: usize = 2;
pub fn gcd(a: BigUint, b: BigUint) -> BigUint {
if a.is_zero() {
return b;
}
if b.is_zero() {
return a;
}
if a.as_limbs().len() <= LEHMER_THRESHOLD_LIMBS || b.as_limbs().len() <= LEHMER_THRESHOLD_LIMBS
{
return gcd_binary(a, b);
}
gcd_lehmer(a, b)
}
pub fn gcd_binary(mut a: BigUint, mut b: BigUint) -> BigUint {
if a.is_zero() {
return b;
}
if b.is_zero() {
return a;
}
let tz_a = a.trailing_zeros();
let tz_b = b.trailing_zeros();
let shift = tz_a.min(tz_b);
a = a.shr_bits(tz_a);
b = b.shr_bits(shift);
while !b.is_zero() {
let tz = b.trailing_zeros();
if tz > 0 {
b = b.shr_bits(tz);
}
if a > b {
swap(&mut a, &mut b);
}
b = b
.checked_sub(&a)
.expect("Stein invariant: a <= b ensures non-negative subtraction");
}
a.shl_bits(shift)
}
fn gcd_lehmer(mut a: BigUint, mut b: BigUint) -> BigUint {
loop {
if b.is_zero() {
return a;
}
if a < b {
swap(&mut a, &mut b);
}
if a.as_limbs().len() <= LEHMER_THRESHOLD_LIMBS
|| b.as_limbs().len() <= LEHMER_THRESHOLD_LIMBS
{
return gcd_binary(a, b);
}
let bits_a = a.bit_length();
debug_assert!(bits_a >= 64, "above-threshold a has >= 3 limbs");
let shift = bits_a - 64;
let ah = top64_at_shift(&a, shift);
let bh = top64_at_shift(&b, shift);
if bh == 0 {
let (_q, r) = divrem(&a, &b);
a = core::mem::replace(&mut b, r);
continue;
}
let q_lo = ah / bh.saturating_add(1);
let q_hi = ah / bh;
if q_lo == q_hi && q_lo > 0 {
let q_big = BigUint::from_u64(q_lo);
let qb = &q_big * &b;
a = a
.checked_sub(&qb)
.expect("Lehmer invariant: q*b <= a by bounding lemma");
swap(&mut a, &mut b);
} else {
let (_q, r) = divrem(&a, &b);
a = core::mem::replace(&mut b, r);
}
}
}
#[inline]
fn top64_at_shift(n: &BigUint, shift: u64) -> u64 {
let shifted = n.shr_bits(shift);
match shifted.as_limbs() {
[] => 0,
[lo] => *lo,
[lo, hi, ..] => {
let hi_bits = 64 - hi.leading_zeros() as u64;
if hi_bits >= 64 {
*hi
} else {
let down = 64 - hi_bits;
(hi << down) | (lo >> hi_bits)
}
}
}
}
pub fn gcd_int(a: &BigInt, b: &BigInt) -> BigInt {
let g = gcd(a.magnitude().clone(), b.magnitude().clone());
BigInt::from(g)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gcd_zero_zero_is_zero() {
assert_eq!(gcd(BigUint::ZERO, BigUint::ZERO), BigUint::ZERO);
}
#[test]
fn gcd_one_arg_zero() {
assert_eq!(
gcd(BigUint::ZERO, BigUint::from_u64(7)),
BigUint::from_u64(7)
);
assert_eq!(
gcd(BigUint::from_u64(7), BigUint::ZERO),
BigUint::from_u64(7)
);
}
#[test]
fn gcd_small() {
assert_eq!(
gcd(BigUint::from_u64(48), BigUint::from_u64(18)),
BigUint::from_u64(6)
);
assert_eq!(
gcd(BigUint::from_u64(100), BigUint::from_u64(75)),
BigUint::from_u64(25)
);
assert_eq!(
gcd(BigUint::from_u64(13), BigUint::from_u64(17)),
BigUint::one()
);
}
#[test]
fn gcd_a_a_is_a() {
let a = BigUint::from_u64(42);
assert_eq!(gcd(a.clone(), a.clone()), a);
}
#[test]
fn gcd_one_n() {
let n = BigUint::from_u64(123_456_789);
assert_eq!(gcd(BigUint::one(), n.clone()), BigUint::one());
assert_eq!(gcd(n, BigUint::one()), BigUint::one());
}
#[test]
fn gcd_binary_matches_gcd_on_small() {
assert_eq!(
gcd_binary(BigUint::from_u64(48), BigUint::from_u64(18)),
gcd(BigUint::from_u64(48), BigUint::from_u64(18))
);
}
#[test]
fn gcd_int_sign_invariant() {
let a = BigInt::from(-12i64);
let b = BigInt::from(-18i64);
assert_eq!(gcd_int(&a, &b), BigInt::from(6i64));
assert_eq!(
gcd_int(&BigInt::zero(), &BigInt::from(-9i64)),
BigInt::from(9i64)
);
}
#[test]
fn gcd_multi_limb_power_of_two() {
let a = BigUint::one().shl_bits(256);
let b = BigUint::one().shl_bits(512);
let g = gcd(a.clone(), b);
assert_eq!(g, a);
}
#[test]
fn gcd_fibonacci_consecutive_pair_is_one() {
let (f_90, f_91) = fib_pair(90);
assert_eq!(
gcd(BigUint::from_u64(f_90), BigUint::from_u64(f_91)),
BigUint::one()
);
let big_a = BigUint::from_u64(f_90).shl_bits(256);
let big_b = BigUint::from_u64(f_91).shl_bits(256);
let g = gcd(big_a, big_b);
assert_eq!(g, BigUint::one().shl_bits(256));
}
fn fib_pair(n: u32) -> (u64, u64) {
let mut a: u64 = 0;
let mut b: u64 = 1;
for _ in 0..n {
let next = a.wrapping_add(b);
a = b;
b = next;
}
(a, b)
}
}