use num_bigint::{BigInt, BigUint, Sign};
use num_integer::Integer;
use num_traits::{One, Signed, Zero};
pub fn balanced_lift(x: &BigUint, m: &BigUint) -> BigInt {
let x = BigInt::from(x.clone());
let m_big = BigInt::from(m.clone());
if &x * 2 > m_big {
x - BigInt::from(m.clone())
} else {
x
}
}
pub fn rational_reconstruct(x: &BigUint, m: &BigUint) -> Option<(BigInt, BigInt)> {
if m.is_zero() {
return None;
}
let x = x % m;
if x.is_zero() {
return Some((BigInt::zero(), BigInt::one()));
}
let bound = BigInt::from((m / 2u32).sqrt());
let m_big = BigInt::from(m.clone());
let x_big = BigInt::from(x);
let mut r0 = m_big.clone();
let mut r1 = x_big.clone();
let mut t0 = BigInt::zero();
let mut t1 = BigInt::one();
while r1 > bound {
let q = &r0 / &r1;
let r2 = &r0 - &q * &r1;
r0 = r1;
r1 = r2;
let t2 = &t0 - &q * &t1;
t0 = t1;
t1 = t2;
}
let mut p = r1;
let mut q = t1;
if q.is_zero() {
return None;
}
if q.sign() == Sign::Minus {
p = -p;
q = -q;
}
if q.abs() > bound || p.abs() > bound {
return None;
}
if p.gcd(&q) != BigInt::one() {
return None;
}
let check = (&p - &q * &x_big).mod_floor(&m_big);
if !check.is_zero() {
return None;
}
Some((p, q))
}
#[cfg(test)]
mod tests {
use super::*;
fn encode(p: i64, q: i64, m: &BigUint) -> BigUint {
let m_big = BigInt::from(m.clone());
let qi = BigInt::from(q);
let inv = mod_inverse_big(&qi, &m_big).unwrap();
let x = (BigInt::from(p) * inv).mod_floor(&m_big);
x.to_biguint().unwrap()
}
fn mod_inverse_big(a: &BigInt, m: &BigInt) -> Option<BigInt> {
let g = a.extended_gcd(m);
if g.gcd != BigInt::one() {
return None;
}
Some(g.x.mod_floor(m))
}
#[test]
fn round_trip() {
let m = BigUint::from(1u32) << 64;
let x = encode(3, 7, &m);
assert_eq!(rational_reconstruct(&x, &m), Some((BigInt::from(3), BigInt::from(7))));
let x = encode(-5, 11, &m);
assert_eq!(rational_reconstruct(&x, &m), Some((BigInt::from(-5), BigInt::from(11))));
}
#[test]
fn integer_round_trip() {
let m = BigUint::from(1u32) << 64;
let x = BigUint::from(42u32);
assert_eq!(rational_reconstruct(&x, &m), Some((BigInt::from(42), BigInt::one())));
}
#[test]
fn out_of_range_fails() {
let m = BigUint::from(50u32);
let x = encode(7, 9, &m); assert_eq!(rational_reconstruct(&x, &m), None);
}
}