use crate::Rational;
use crate::rational::arithmetic::cfrac_helpers::{
Mat22, RECONSTRUCT_HGCD_CUTOFF, fmms1, fmpq_hgcd, hgcd_word, left_shift_hi, limbs_at_most,
limbs_cmp,
};
use alloc::vec;
use alloc::vec::Vec;
use core::cmp::Ordering;
use core::mem::{replace, swap, take};
use malachite_base::num::arithmetic::traits::{
AddMul, AddMulAssign, CoprimeWith, DivMod, FloorSqrt, ModPowerOf2, Parity, SubMulAssign,
};
use malachite_base::num::basic::integers::PrimitiveInt;
use malachite_base::num::basic::traits::{One, Zero};
use malachite_base::num::conversion::traits::{JoinHalves, SplitInHalf, WrappingFrom};
use malachite_base::num::logic::traits::NotAssign;
use malachite_nz::natural::Natural;
use malachite_nz::natural::arithmetic::add_mul::limbs_slice_add_mul_limb_same_length_in_place_left;
use malachite_nz::natural::arithmetic::mul::limb::{
limbs_mul_limb_to_out, limbs_slice_mul_limb_in_place,
};
use malachite_nz::platform::{DoubleLimb, Limb};
fn reconstruct_limb(a: Limb, m: Limb, n_bound: Limb, d_bound: &Natural) -> Option<Rational> {
let mut big_a = m;
let mut big_b = a;
let mut m11: Limb = 1;
let mut m12: Limb = 0;
let mut mdet_pos = true;
loop {
debug_assert!(big_a > big_b && big_b > n_bound);
let (q, r) = big_a.div_mod(big_b);
m12.add_mul_assign(m11, q);
swap(&mut m11, &mut m12);
mdet_pos.not_assign();
big_a = big_b;
big_b = r;
if big_b <= n_bound {
break;
}
}
if *d_bound < m11 || !big_b.coprime_with(m11) {
return None;
}
Some(Rational {
sign: mdet_pos || big_b == 0,
numerator: Natural::from(big_b),
denominator: Natural::from(m11),
})
}
fn reconstruct_double_limb(
a: DoubleLimb,
m: DoubleLimb,
n_bound: DoubleLimb,
d_bound: &Natural,
) -> Option<Rational> {
let mut big_a = m;
let mut big_b = a;
let mut m11: DoubleLimb = 1;
let mut m12: DoubleLimb = 0;
let mut mdet_pos = true;
loop {
debug_assert!(big_a > big_b && big_b > n_bound);
let (q, r) = if big_a >> Limb::WIDTH == 0 {
let (q, r) = Limb::wrapping_from(big_a).div_mod(Limb::wrapping_from(big_b));
(DoubleLimb::from(q), DoubleLimb::from(r))
} else {
big_a.div_mod(big_b)
};
m12.add_mul_assign(m11, q);
swap(&mut m11, &mut m12);
mdet_pos.not_assign();
big_a = big_b;
big_b = r;
if big_b <= n_bound {
break;
}
}
if *d_bound < m11 || !big_b.coprime_with(m11) {
return None;
}
Some(Rational {
sign: mdet_pos || big_b == 0,
numerator: Natural::from(big_b),
denominator: Natural::from(m11),
})
}
fn reconstruct_helper(
a: Natural,
m: Natural,
n_bound: &Natural,
d_bound: &Natural,
) -> Option<Rational> {
assert!(a < m, "a must be reduced mod m");
assert_ne!(*n_bound, 0u32, "n_bound must be positive");
assert_ne!(*d_bound, 0u32, "d_bound must be positive");
if a <= *n_bound {
return Some(Rational::from(a));
}
let diff = &m - &a;
if diff <= *n_bound {
return Some(-Rational::from(diff));
}
let m_limbs = m.limb_count();
if m_limbs == 1 {
return reconstruct_limb(
Limb::wrapping_from(&a),
Limb::wrapping_from(&m),
Limb::wrapping_from(n_bound),
d_bound,
);
}
if m_limbs == 2 {
return reconstruct_double_limb(
DoubleLimb::wrapping_from(&a),
DoubleLimb::wrapping_from(&m),
DoubleLimb::wrapping_from(n_bound),
d_bound,
);
}
if m_limbs <= const { ARRAY_LIMIT as u64 } {
return reconstruct_array(&a, &m, n_bound, d_bound);
}
let mut r = Reduction::new(m, a);
let gap = m_limbs - n_bound.limb_count();
if gap >= 3 && !(gap >= RECONSTRUCT_HGCD_CUTOFF && r.split_loop(n_bound)) {
r.lehmer_loop(n_bound);
}
if r.big_b > *n_bound {
r.gauss_loop(n_bound);
}
r.finish(d_bound)
}
const ARRAY_LIMIT: usize = 12;
fn reconstruct_array(
a: &Natural,
m: &Natural,
n_bound: &Natural,
d_bound: &Natural,
) -> Option<Rational> {
let mut big_a = [0 as Limb; ARRAY_LIMIT + 1];
let mut big_b = [0 as Limb; ARRAY_LIMIT + 1];
let mut q_arr = [0 as Limb; ARRAY_LIMIT + 1];
let mut r_arr = [0 as Limb; ARRAY_LIMIT + 1];
let mut m11 = [0 as Limb; ARRAY_LIMIT + 2];
let mut m12 = [0 as Limb; ARRAY_LIMIT + 2];
let m_limbs = m.to_limbs_asc();
let mut a_len = m_limbs.len();
big_a[..a_len].copy_from_slice(&m_limbs);
let a_limbs = a.to_limbs_asc();
let mut b_len = a_limbs.len();
big_b[..b_len].copy_from_slice(&a_limbs);
let n_limbs = n_bound.to_limbs_asc();
let n_len = n_limbs.len();
let n_lz = n_limbs.last().unwrap().leading_zeros();
let mut m_len = 1;
m11[0] = 1;
let mut m_len_12 = 1;
let mut mdet_pos = true;
'outer: loop {
loop {
debug_assert!(a_len > 0 && big_a[a_len - 1] > 0);
debug_assert!(b_len > 0 && big_b[b_len - 1] > 0);
debug_assert!(limbs_cmp(&big_a[..a_len], &big_b[..b_len]) == Ordering::Greater);
debug_assert!(limbs_cmp(&big_b[..b_len], &n_limbs) == Ordering::Greater);
if a_len < 3 || b_len <= n_len {
break;
}
let a_lz = big_a[a_len - 1].leading_zeros();
if a_len - 1 > b_len {
break;
}
if a_len - 1 == n_len && n_lz < a_lz {
break;
}
debug_assert!(a_len == b_len || a_len - 1 == b_len);
big_b[a_len - 1] &= if a_len - 1 == b_len { 0 } else { Limb::MAX };
let a1 = left_shift_hi(big_a[a_len - 1], big_a[a_len - 2], a_lz);
let a0 = left_shift_hi(big_a[a_len - 2], big_a[a_len - 3], a_lz);
let b1 = left_shift_hi(big_b[a_len - 1], big_b[a_len - 2], a_lz);
let b0 = left_shift_hi(big_b[a_len - 2], big_b[a_len - 3], a_lz);
let (written, h) = hgcd_word(
DoubleLimb::join_halves(a1, a0),
DoubleLimb::join_halves(b1, b0),
);
if written == 0 {
break;
}
let (q_len, r_len) = if h.det_pos {
(
fmms1(&mut q_arr, h.m22, &big_a, h.m12, &big_b, a_len),
fmms1(&mut r_arr, h.m11, &big_b, h.m21, &big_a, a_len),
)
} else {
(
fmms1(&mut q_arr, h.m12, &big_b, h.m22, &big_a, a_len),
fmms1(&mut r_arr, h.m21, &big_a, h.m11, &big_b, a_len),
)
};
debug_assert!(q_len >= r_len);
if limbs_at_most(&q_arr[..q_len], &n_limbs) {
break;
}
a_len = q_len;
b_len = r_len;
big_a = q_arr;
big_b = r_arr;
if !h.det_pos {
mdet_pos.not_assign();
}
let row_len = m_len.max(m_len_12);
let ex0 = limbs_mul_limb_to_out::<DoubleLimb, Limb>(
&mut r_arr[..row_len],
&m11[..row_len],
h.m11,
);
let ex1 = limbs_slice_add_mul_limb_same_length_in_place_left(
&mut r_arr[..row_len],
&m12[..row_len],
h.m21,
);
let ex2 = limbs_slice_mul_limb_in_place(&mut m12[..row_len], h.m22);
let ex3 = limbs_slice_add_mul_limb_same_length_in_place_left(
&mut m12[..row_len],
&m11[..row_len],
h.m12,
);
let sum = DoubleLimb::from(ex2) + DoubleLimb::from(ex3);
(m12[row_len + 1], m12[row_len]) = sum.split_in_half();
m11[..row_len].copy_from_slice(&r_arr[..row_len]);
let sum = DoubleLimb::from(ex0) + DoubleLimb::from(ex1);
(m11[row_len + 1], m11[row_len]) = sum.split_in_half();
m_len = row_len
+ if m11[row_len + 1] != 0 {
2
} else {
usize::from(m11[row_len] != 0)
};
m_len_12 = row_len
+ if m12[row_len + 1] != 0 {
2
} else {
usize::from(m12[row_len] != 0)
};
if limbs_at_most(&big_b[..b_len], &n_limbs) {
break 'outer;
}
}
let an = Natural::from_limbs_asc(&big_a[..a_len]);
let bn = Natural::from_limbs_asc(&big_b[..b_len]);
let (qn, rn) = an.div_mod(&bn);
big_a = [0; ARRAY_LIMIT + 1];
let b_limbs = bn.into_limbs_asc();
a_len = b_limbs.len();
big_a[..a_len].copy_from_slice(&b_limbs);
big_b = [0; ARRAY_LIMIT + 1];
let r_limbs = rn.into_limbs_asc();
b_len = r_limbs.len();
big_b[..b_len].copy_from_slice(&r_limbs);
let m11n = Natural::from_limbs_asc(&m11[..m_len]);
let new_m11 = Natural::from_limbs_asc(&m12[..m_len_12]).add_mul(&m11n, qn);
m12 = [0; ARRAY_LIMIT + 2];
let m11_limbs = m11n.into_limbs_asc();
m_len_12 = m11_limbs.len();
m12[..m_len_12].copy_from_slice(&m11_limbs);
m11 = [0; ARRAY_LIMIT + 2];
let new_limbs = new_m11.into_limbs_asc();
m_len = new_limbs.len();
m11[..m_len].copy_from_slice(&new_limbs);
mdet_pos.not_assign();
if limbs_at_most(&big_b[..b_len], &n_limbs) {
break;
}
}
finish(
Natural::from_limbs_asc(&big_b[..b_len]),
Natural::from_limbs_asc(&m11[..m_len]),
mdet_pos,
d_bound,
)
}
enum LehmerOutcome {
Exhausted,
OneStepNeeded,
Finished,
}
struct Reduction {
big_a: Natural,
big_b: Natural,
m11: Natural,
m12: Natural,
det_pos: bool,
}
impl Reduction {
const fn new(big_a: Natural, big_b: Natural) -> Self {
Self {
big_a,
big_b,
m11: Natural::ONE,
m12: Natural::ZERO,
det_pos: true,
}
}
fn gauss_step(&mut self) {
let (q, r) = take(&mut self.big_a).div_mod(&self.big_b);
self.m12.add_mul_assign(&self.m11, q);
swap(&mut self.m11, &mut self.m12);
self.det_pos.not_assign();
self.big_a = replace(&mut self.big_b, r);
}
fn gauss_loop(&mut self, n_bound: &Natural) {
loop {
self.gauss_step();
if self.big_b <= *n_bound {
break;
}
}
}
#[cfg_attr(dylint_lib = "malachite_lints", allow(adjacent_vec_allocations))]
fn lehmer_step(&mut self, n_bound: &Natural) -> LehmerOutcome {
let n_limbs = n_bound.to_limbs_asc();
let n_len = n_limbs.len();
let n_lz = n_limbs.last().unwrap().leading_zeros();
let mut a = self.big_a.to_limbs_asc();
let mut b = self.big_b.to_limbs_asc();
let mut a_len = a.len();
let mut b_len = b.len();
if a_len < 3 || b_len <= n_len {
return LehmerOutcome::Exhausted;
}
let capacity = a_len;
b.resize(capacity, 0);
let mut s = vec![0 as Limb; capacity];
let mut t = vec![0 as Limb; capacity];
let ret = loop {
debug_assert!(a_len >= b_len && b_len >= n_len && n_len > 0);
debug_assert!(a[a_len - 1] != 0 && b[b_len - 1] != 0);
if a_len < 3 || b_len <= n_len {
break LehmerOutcome::Exhausted;
}
if a_len - 1 > b_len {
break LehmerOutcome::OneStepNeeded;
}
let a_lz = a[a_len - 1].leading_zeros();
if a_len - 1 == n_len && n_lz < a_lz {
break LehmerOutcome::Exhausted;
}
debug_assert!(a_len == b_len || a_len - 1 == b_len);
if a_len - 1 == b_len {
b[a_len - 1] = 0;
}
let a1 = left_shift_hi(a[a_len - 1], a[a_len - 2], a_lz);
let a0 = left_shift_hi(a[a_len - 2], a[a_len - 3], a_lz);
let b1 = left_shift_hi(b[a_len - 1], b[a_len - 2], a_lz);
let b0 = left_shift_hi(b[a_len - 2], b[a_len - 3], a_lz);
let (written, h) = hgcd_word(
DoubleLimb::join_halves(a1, a0),
DoubleLimb::join_halves(b1, b0),
);
if written == 0 {
break LehmerOutcome::OneStepNeeded;
}
let (s_len, t_len) = if h.det_pos {
(
fmms1(&mut s, h.m22, &a, h.m12, &b, a_len),
fmms1(&mut t, h.m11, &b, h.m21, &a, a_len),
)
} else {
(
fmms1(&mut s, h.m12, &b, h.m22, &a, a_len),
fmms1(&mut t, h.m21, &a, h.m11, &b, a_len),
)
};
debug_assert!(s_len >= t_len);
if limbs_at_most(&s[..s_len], &n_limbs) {
break LehmerOutcome::OneStepNeeded;
}
let new_m11 =
(&self.m11 * Natural::from(h.m11)).add_mul(&self.m12, Natural::from(h.m21));
self.m12 *= Natural::from(h.m22);
self.m12.add_mul_assign(&self.m11, Natural::from(h.m12));
self.m11 = new_m11;
if !h.det_pos {
self.det_pos.not_assign();
}
swap(&mut a, &mut s);
swap(&mut b, &mut t);
a_len = s_len;
b_len = t_len;
if limbs_at_most(&b[..b_len], &n_limbs) {
break LehmerOutcome::Finished;
}
};
a.truncate(a_len);
self.big_a = Natural::from_owned_limbs_asc(a);
b.truncate(b_len);
self.big_b = Natural::from_owned_limbs_asc(b);
ret
}
fn lehmer_loop(&mut self, n_bound: &Natural) {
loop {
match self.lehmer_step(n_bound) {
LehmerOutcome::Exhausted | LehmerOutcome::Finished => return,
LehmerOutcome::OneStepNeeded => {}
}
self.gauss_step();
if self.big_b <= *n_bound {
return;
}
}
}
fn split_loop(&mut self, n_bound: &Natural) -> bool {
let n_size = n_bound.limb_count();
let mut v = Vec::new();
loop {
debug_assert!(self.big_a > self.big_b && self.big_b > *n_bound);
let a_size = self.big_a.limb_count();
let b_size = self.big_b.limb_count();
if b_size - n_size < RECONSTRUCT_HGCD_CUTOFF {
return false;
}
let s_limbs = 1 + (n_size << 1).saturating_sub(a_size);
if s_limbs < b_size {
let shift_bits = s_limbs << Limb::LOG_WIDTH;
let mut a_top = &self.big_a >> shift_bits;
let mut b_top = &self.big_b >> shift_bits;
if a_top > b_top {
let mut h = Mat22::one();
v.clear();
fmpq_hgcd(Some(&mut v), &mut h, &mut a_top, &mut b_top);
if !h.is_one() {
let q = (&self.big_a).mod_power_of_2(shift_bits);
let r = (&self.big_b).mod_power_of_2(shift_bits);
self.big_a = a_top << shift_bits;
self.big_b = b_top << shift_bits;
if h.det_pos {
self.big_a.add_mul_assign(&q, &h.m22);
self.big_a.sub_mul_assign(&r, &h.m12);
self.big_b.add_mul_assign(&r, &h.m11);
self.big_b.sub_mul_assign(&q, &h.m21);
} else {
self.big_a.add_mul_assign(&r, &h.m12);
self.big_a.sub_mul_assign(&q, &h.m22);
self.big_b.add_mul_assign(&q, &h.m21);
self.big_b.sub_mul_assign(&r, &h.m11);
}
let new_m11 = (&self.m11 * &h.m11).add_mul(&self.m12, &h.m21);
self.m12 *= &h.m22;
self.m12.add_mul_assign(&self.m11, &h.m12);
self.m11 = new_m11;
if !h.det_pos {
self.det_pos.not_assign();
}
while self.big_a <= *n_bound {
let q = v.pop().unwrap();
self.big_b.add_mul_assign(&self.big_a, &q);
swap(&mut self.big_a, &mut self.big_b);
self.m11.sub_mul_assign(&self.m12, &q);
swap(&mut self.m11, &mut self.m12);
self.det_pos.not_assign();
}
debug_assert!(!v.is_empty());
if self.big_b > *n_bound {
continue;
}
return true;
}
}
}
self.gauss_step();
if self.big_b <= *n_bound {
return true;
}
}
}
fn finish(self, d_bound: &Natural) -> Option<Rational> {
finish(self.big_b, self.m11, self.det_pos, d_bound)
}
}
fn finish(big_b: Natural, m11: Natural, mdet_pos: bool, d_bound: &Natural) -> Option<Rational> {
if m11 > *d_bound || !(&big_b).coprime_with(&m11) {
return None;
}
Some(Rational {
sign: mdet_pos || big_b == 0u32,
numerator: big_b,
denominator: m11,
})
}
fn balanced_bound(m: &Natural) -> Natural {
let mut b = m >> 1u32;
if m.even() {
b -= Natural::ONE;
}
b.floor_sqrt()
}
impl Rational {
#[inline]
pub fn reconstruct_with_bounds(
a: Natural,
m: Natural,
n_bound: &Natural,
d_bound: &Natural,
) -> Option<Self> {
reconstruct_helper(a, m, n_bound, d_bound)
}
#[inline]
pub fn reconstruct_with_bounds_ref(
a: &Natural,
m: &Natural,
n_bound: &Natural,
d_bound: &Natural,
) -> Option<Self> {
reconstruct_helper(a.clone(), m.clone(), n_bound, d_bound)
}
pub fn reconstruct(a: Natural, m: Natural) -> Option<Self> {
assert!(m > 2u32, "m must be greater than 2");
let b = balanced_bound(&m);
reconstruct_helper(a, m, &b, &b)
}
pub fn reconstruct_ref(a: &Natural, m: &Natural) -> Option<Self> {
assert!(*m > 2u32, "m must be greater than 2");
let b = balanced_bound(m);
reconstruct_helper(a.clone(), m.clone(), &b, &b)
}
}