use num_bigint::{BigInt, BigUint, Sign};
use num_integer::Integer;
use num_rational::BigRational;
use num_traits::{One, Signed, Zero};
fn window(digits: impl Iterator<Item = u64>, shift: u64) -> i128 {
let mut digits = digits.skip((shift / 64) as usize);
let lo = u128::from(digits.next().unwrap_or(0));
let hi = u128::from(digits.next().unwrap_or(0));
((hi << 64 | lo) >> (shift % 64)) as i128 & i128::from(i64::MAX)
}
fn run(u: &BigUint, v: &BigUint, limit: i128) -> Option<[i64; 4]> {
let shift = u.bits().saturating_sub(63);
let (x, y) = (
window(u.iter_u64_digits(), shift),
window(v.iter_u64_digits(), shift),
);
cofactors(x, y, limit)
}
fn cofactors(mut x: i128, mut y: i128, limit: i128) -> Option<[i64; 4]> {
let (mut a, mut b, mut c, mut d) = (1i128, 0i128, 0i128, 1i128);
let quotient = |n: i128, m: i128| (n >= 0 && m > 0).then(|| i128::from(n as u64 / m as u64));
while let (Some(q), Some(r)) = (quotient(x + a, y + c), quotient(x + b, y + d)) {
if q != r || (b - q * d).abs() > limit {
break;
}
(a, c) = (c, a - q * c);
(b, d) = (d, b - q * d);
(x, y) = (y, x - q * y);
}
(b != 0).then(|| [a, b, c, d].map(|t| t as i64))
}
fn apply(m: [i64; 4], u: &BigInt, v: &BigInt) -> (BigInt, BigInt) {
(u * m[0] + v * m[1], u * m[2] + v * m[3])
}
fn nonnegative(x: BigInt) -> BigUint {
x.into_parts().1
}
const WORDS: usize = 8;
type Words = [u64; WORDS];
fn bits(x: &Words) -> u64 {
x.iter()
.rposition(|&w| w != 0)
.map_or(0, |i| 64 * i as u64 + u64::from(64 - x[i].leading_zeros()))
}
fn apply_words(m: [i64; 4], u: &Words, v: &Words) -> (Words, Words) {
let combine = |a: i64, b: i64| {
let (mut out, mut carry) = ([0; WORDS], 0i128);
for i in 0..WORDS {
let s = i128::from(a) * i128::from(u[i]) + i128::from(b) * i128::from(v[i]) + carry;
out[i] = s as u64;
carry = s >> 64;
}
out
};
(combine(m[0], m[1]), combine(m[2], m[3]))
}
fn words(x: &BigUint) -> Option<Words> {
let mut out = [0; WORDS];
for (i, d) in x.iter_u64_digits().enumerate() {
*out.get_mut(i)? = d;
}
Some(out)
}
fn gcd_words(mut u: Words, mut v: Words) -> BigUint {
while bits(&v) > 64 {
let shift = bits(&u).saturating_sub(63);
let m = cofactors(
window(u.iter().copied(), shift),
window(v.iter().copied(), shift),
i128::from(i64::MAX),
);
if let Some(m) = m {
(u, v) = apply_words(m, &u, &v);
} else {
let (a, b) = (
BigUint::from_slice(&to_u32(&u)),
BigUint::from_slice(&to_u32(&v)),
);
let r = a % &b;
(u, v) = (v, words(&r).unwrap_or_else(|| unreachable!("r < v")));
}
}
let y = v[0];
if y == 0 {
return BigUint::from_slice(&to_u32(&u));
}
let top = bits(&u).div_ceil(64) as usize;
let x = u[..top].iter().rev().fold(0, |r, &w| {
((u128::from(r) << 64 | u128::from(w)) % u128::from(y)) as u64
});
euclid(x, y).into()
}
fn to_u32(x: &Words) -> Vec<u32> {
x.iter()
.flat_map(|&w| [w as u32, (w >> 32) as u32])
.collect()
}
const fn euclid(mut x: u64, mut y: u64) -> u64 {
while x != 0 {
(x, y) = (y % x, x);
}
y
}
fn finish(u: BigUint, y: u64) -> BigUint {
if y == 0 {
return u;
}
euclid((u % y).iter_u64_digits().next().unwrap_or(0), y).into()
}
pub fn gcd(a: &BigUint, b: &BigUint) -> BigUint {
if let (Some(x), Some(y)) = (words(a), words(b)) {
return if a >= b {
gcd_words(x, y)
} else {
gcd_words(y, x)
};
}
let (mut u, mut v) = if a >= b {
(a.clone(), b.clone())
} else {
(b.clone(), a.clone())
};
while v.bits() > 64 {
if let Some(m) = run(&u, &v, i128::from(i64::MAX)) {
let (s, t) = apply(m, &u.into(), &v.into());
(u, v) = (nonnegative(s), nonnegative(t));
} else {
(u, v) = (v.clone(), u % v);
}
}
finish(u, v.iter_u64_digits().next().unwrap_or(0))
}
pub fn fraction(n: BigInt, d: &BigUint) -> BigRational {
let g = gcd(n.magnitude(), d);
let (sign, n) = n.into_parts();
BigRational::new_raw(BigInt::from_biguint(sign, n / &g), BigInt::from(d / g))
}
pub(crate) fn wang(x: &BigInt, m: &BigInt, bound: &BigInt) -> Option<BigRational> {
let (mut r0, mut r1) = (m.clone(), x.mod_floor(m));
let (mut s0, mut s1) = (BigInt::zero(), BigInt::from(1u32));
let bits = bound.bits();
while &r1 > bound {
let limit = r0.bits().saturating_sub(bits + 2).min(62);
if let Some(m) = (limit > 0)
.then(|| run(r0.magnitude(), r1.magnitude(), 1 << limit))
.flatten()
{
(r0, r1) = apply(m, &r0, &r1);
(s0, s1) = apply(m, &s0, &s1);
} else {
let (q, r) = r0.div_rem(&r1);
r0 = std::mem::replace(&mut r1, r);
let s = &s0 - q * &s1;
s0 = std::mem::replace(&mut s1, s);
}
}
if s1.is_zero() || s1.magnitude() > bound.magnitude() {
return None;
}
if !gcd(r1.magnitude(), s1.magnitude()).is_one() {
return None;
}
let sign = if s1.sign() == Sign::Minus { -r1 } else { r1 };
Some(BigRational::new_raw(sign, s1.abs()))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crt;
const XORSHIFT_SEED: u64 = 0x9e37_79b9_7f4a_7c15;
fn samples() -> impl Iterator<Item = BigUint> {
let mut s = XORSHIFT_SEED;
(0..400).map(move |i| {
let mut next = || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
};
let words = 1 + i % 24;
let mut x = BigUint::zero();
for _ in 0..words {
x = (x << 64u32) + next();
}
x >> (next() % 64)
})
}
#[test]
fn gcd_matches_euclid() {
let xs: Vec<_> = samples().collect();
for (a, b) in xs.iter().zip(xs.iter().skip(1)) {
let common = &xs[a.bits() as usize % xs.len()];
let (a, b) = (a * common, b * common);
assert_eq!(gcd(&a, &b), a.gcd(&b));
assert_eq!(gcd(&a, &BigUint::zero()), a);
}
}
fn plain_wang(x: &BigInt, m: &BigInt, bound: &BigInt) -> Option<BigRational> {
let (mut r0, mut r1) = (m.clone(), x.mod_floor(m));
let (mut s0, mut s1) = (BigInt::zero(), BigInt::one());
while &r1 > bound {
let (q, r) = r0.div_rem(&r1);
r0 = std::mem::replace(&mut r1, r);
let s = &s0 - q * &s1;
s0 = std::mem::replace(&mut s1, s);
}
(!s1.is_zero() && s1.abs() <= *bound && r1.gcd(&s1).is_one())
.then(|| BigRational::new(r1, s1))
}
#[test]
fn wang_matches_plain_reconstruction() {
let m: BigInt = samples()
.take(8)
.fold(BigUint::from(1u32), |m, x| m * (x | BigUint::from(1u32)))
.into();
let context = crt::WangContext::new(&m).unwrap();
let bound = (&m / 2u32).sqrt();
for (n, d) in samples().zip(samples().skip(7)) {
let (n, d) = (BigInt::from(n) % &bound, BigInt::from(d) % &bound + 1u32);
let Some(inv) = d.modinv(&m) else { continue };
for x in [(&n * inv).mod_floor(&m), &n * 7 + &d] {
assert_eq!(context.reconstruct(&x), plain_wang(&x, &m, &bound));
}
}
}
}