use ocas_domain::{Domain, EuclideanDomain, Integer, IntegerDomain};
pub fn rational_reconstruction(a: &Integer, m: &Integer) -> Option<(Integer, Integer)> {
let d = IntegerDomain;
if d.is_zero(m) {
return None;
}
let (_, a_red_raw) = d.div_rem(a, m)?;
let a_red = if is_negative(&a_red_raw) {
d.add(&a_red_raw, m)
} else {
a_red_raw
};
if d.is_zero(&a_red) {
return Some((d.zero(), d.one()));
}
let mut r0 = m.clone();
let mut r1 = a_red.clone();
let mut t0 = d.zero();
let mut t1 = d.one();
let m_half = d.div(m, &Integer::from(2)).unwrap_or_else(|| m.clone());
let bound = integer_sqrt(&m_half);
loop {
if d.is_zero(&r1) {
return None;
}
if abs_le(&r1, &bound) && abs_le(&t1, &bound) {
let check = d.sub(&d.mul(&a_red, &t1), &r1);
let (_, rem) = d.div_rem(&check, m)?;
if d.is_zero(&rem) && !d.is_zero(&t1) {
let (n, d_val) = if is_negative(&t1) {
(d.neg(&r1), d.neg(&t1))
} else {
(r1, t1)
};
return Some((n, d_val));
}
return None;
}
let (q, r_new) = d.div_rem(&r0, &r1)?;
let t_new = d.sub(&t0, &d.mul(&q, &t1));
r0 = r1;
r1 = r_new;
t0 = t1;
t1 = t_new;
}
}
fn is_negative(a: &Integer) -> bool {
a < &Integer::from(0)
}
fn abs_le(a: &Integer, b: &Integer) -> bool {
let d = IntegerDomain;
let abs_a = if is_negative(a) { d.neg(a) } else { a.clone() };
&abs_a <= b
}
fn integer_sqrt(n: &Integer) -> Integer {
let d = IntegerDomain;
if d.is_zero(n) {
return d.zero();
}
if n <= &Integer::from(1) {
return n.clone();
}
let two = Integer::from(2);
let mut x = n.clone();
loop {
let n_div_x = d.div(n, &x).unwrap_or(d.zero());
let sum = d.add(&x, &n_div_x);
let next = d.div(&sum, &two).unwrap_or(x.clone());
if next >= x {
break;
}
x = next;
}
let one = Integer::from(1);
while d.mul(&x, &x) > *n && x > one {
x = d.sub(&x, &one);
}
x
}
#[cfg(test)]
mod tests {
use super::*;
use ocas_domain::{Domain, EuclideanDomain, Integer, IntegerDomain};
fn int(i: i64) -> Integer {
Integer::from(i)
}
#[test]
fn rational_reconstruction_basic() {
let m = int(101);
let d_inv_7 = int(29); let a = IntegerDomain
.div_rem(&IntegerDomain.mul(&int(3), &d_inv_7), &m)
.unwrap()
.1;
let result = rational_reconstruction(&a, &m);
assert!(result.is_some());
let (n, d) = result.unwrap();
let check = IntegerDomain.sub(&IntegerDomain.mul(&a, &d), &n);
let (_, rem) = IntegerDomain.div_rem(&check, &m).unwrap();
assert!(IntegerDomain.is_zero(&rem));
assert!(!IntegerDomain.is_zero(&d));
}
#[test]
fn rational_reconstruction_zero() {
let result = rational_reconstruction(&int(0), &int(100));
assert_eq!(result, Some((int(0), int(1))));
}
#[test]
fn rational_reconstruction_trivial() {
let result = rational_reconstruction(&int(5), &int(101));
assert!(result.is_some());
let (n, d) = result.unwrap();
assert_eq!(d, int(1));
assert_eq!(n, int(5));
}
#[test]
fn rational_reconstruction_failure() {
let result = rational_reconstruction(&int(50), &int(100));
assert!(result.is_none());
}
#[test]
fn rational_reconstruction_zero_modulus() {
let result = rational_reconstruction(&int(5), &int(0));
assert!(result.is_none());
}
#[test]
fn rational_reconstruction_one_half() {
let m = int(101);
let a = int(51);
let result = rational_reconstruction(&a, &m);
assert!(result.is_some());
let (n, d) = result.unwrap();
let check = IntegerDomain.sub(&IntegerDomain.mul(&a, &d), &n);
let (_, rem) = IntegerDomain.div_rem(&check, &m).unwrap();
assert!(IntegerDomain.is_zero(&rem));
assert!(!IntegerDomain.is_zero(&d));
}
#[test]
fn rational_reconstruction_two_thirds() {
let m = int(101);
let a = int(68);
let result = rational_reconstruction(&a, &m);
assert!(result.is_some());
let (n, d) = result.unwrap();
let check = IntegerDomain.sub(&IntegerDomain.mul(&a, &d), &n);
let (_, rem) = IntegerDomain.div_rem(&check, &m).unwrap();
assert!(IntegerDomain.is_zero(&rem));
assert!(!IntegerDomain.is_zero(&d));
}
#[test]
fn integer_sqrt_basic() {
assert_eq!(integer_sqrt(&int(0)), int(0));
assert_eq!(integer_sqrt(&int(1)), int(1));
assert_eq!(integer_sqrt(&int(4)), int(2));
assert_eq!(integer_sqrt(&int(9)), int(3));
assert_eq!(integer_sqrt(&int(10)), int(3)); assert_eq!(integer_sqrt(&int(50)), int(7)); }
}