use crate::natural::arithmetic::add::{
limbs_add_to_out_aliased, limbs_slice_add_greater_in_place_left, limbs_slice_add_limb_in_place,
};
use crate::natural::arithmetic::add_mul::limbs_slice_add_mul_limb_same_length_in_place_left;
use crate::natural::arithmetic::div_mod::{
limbs_div_limb_to_out_mod, limbs_div_mod_qs_to_out_rs_to_ns,
};
use crate::natural::arithmetic::gcd::matrix_2_2::limbs_matrix_2_2_mul;
use crate::natural::arithmetic::mul::limb::{limbs_mul_limb_to_out, limbs_slice_mul_limb_in_place};
use crate::natural::arithmetic::mul::mul_mod::{
limbs_mul_mod_base_pow_n_minus_1, limbs_mul_mod_base_pow_n_minus_1_next_size,
limbs_mul_mod_base_pow_n_minus_1_scratch_len,
};
use crate::natural::arithmetic::mul::{
limbs_mul_greater_to_out, limbs_mul_greater_to_out_scratch_len, limbs_mul_to_out,
limbs_mul_to_out_scratch_len,
};
use crate::natural::arithmetic::shr::limbs_slice_shr_in_place;
use crate::natural::arithmetic::sub::{
limbs_sub_greater_in_place_left, limbs_sub_limb_in_place, limbs_sub_same_length_in_place_left,
};
use crate::natural::arithmetic::sub_mul::limbs_sub_mul_limb_same_length_in_place_left;
use crate::natural::comparison::cmp::limbs_cmp_same_length;
use crate::natural::{bit_to_limb_count_floor, limb_to_bit_count};
use crate::platform::{DoubleLimb, Limb};
use core::cmp::{Ordering::*, max, min};
use core::mem::swap;
use malachite_base::fail_on_untested_path;
use malachite_base::num::arithmetic::traits::{
DivMod, Gcd, Parity, WrappingAddAssign, XMulYToZZ, XXDivModYToQR, XXSubYYToZZ,
};
use malachite_base::num::basic::integers::PrimitiveInt;
use malachite_base::num::conversion::traits::{ExactFrom, JoinHalves, SplitInHalf, WrappingFrom};
use malachite_base::num::logic::traits::{LeadingZeros, NotAssign, TrailingZeros};
use malachite_base::slices::{slice_set_zero, slice_test_zero, slice_trailing_zeros};
pub(crate) trait GcdSubdivideStepContext {
fn gcd_subdiv_step_hook(
&mut self,
g: Option<&[Limb]>,
q: Option<&mut [Limb]>,
q_len: usize,
d: i8,
);
fn gcd_subdiv_step_hook_with_1(&mut self, d: i8) {
self.gcd_subdiv_step_hook(None, Some(&mut [1]), 1, d);
}
}
struct GcdContext<'a>(&'a mut [Limb]);
impl GcdSubdivideStepContext for GcdContext<'_> {
fn gcd_subdiv_step_hook(
&mut self,
g: Option<&[Limb]>,
_q: Option<&mut [Limb]>,
_q_len: usize,
_d: i8,
) {
if let Some(g) = g {
self.0[..g.len()].copy_from_slice(g);
}
}
}
#[cfg(feature = "test_build")]
pub struct HalfGcdMatrix<'a> {
pub(crate) data: &'a mut [Limb],
pub(crate) s: usize,
pub(crate) two_s: usize,
pub(crate) three_s: usize,
pub(crate) n: usize,
}
#[cfg(not(feature = "test_build"))]
pub(crate) struct HalfGcdMatrix<'a> {
data: &'a mut [Limb],
pub(crate) s: usize,
two_s: usize,
three_s: usize,
pub(crate) n: usize,
}
impl HalfGcdMatrix<'_> {
pub_crate_test! {get(&self, row: u8, column: u8) -> &[Limb] {
match (row, column) {
(0, 0) => &self.data[..self.s],
(0, 1) => &self.data[self.s..self.two_s],
(1, 0) => &self.data[self.two_s..self.three_s],
(1, 1) => &self.data[self.three_s..],
_ => panic!(),
}
}}
pub_test! {get_mut(&mut self, row: u8, column: u8) -> &mut [Limb] {
match (row, column) {
(0, 0) => &mut self.data[..self.s],
(0, 1) => &mut self.data[self.s..self.two_s],
(1, 0) => &mut self.data[self.two_s..self.three_s],
(1, 1) => &mut self.data[self.three_s..],
_ => panic!(),
}
}}
#[inline]
fn get_two_mut(
&mut self,
row_1: u8,
column_1: u8,
row_2: u8,
column_2: u8,
) -> (&mut [Limb], &mut [Limb]) {
match (row_1, column_1, row_2, column_2) {
(0, 0, 0, 1) => self.data[..self.two_s].split_at_mut(self.s),
(0, 1, 0, 0) => {
let (xs, ys) = self.data[..self.two_s].split_at_mut(self.s);
(ys, xs)
}
(1, 0, 1, 1) => self.data[self.two_s..].split_at_mut(self.s),
(1, 1, 1, 0) => {
let (xs, ys) = self.data[self.two_s..].split_at_mut(self.s);
(ys, xs)
}
_ => panic!(),
}
}
#[inline]
pub(crate) fn get_four(&mut self) -> (&[Limb], &[Limb], &[Limb], &[Limb]) {
split_into_chunks!(self.data, self.s, [x00, x01, x10], x11);
(x00, x01, x10, x11)
}
#[inline]
fn get_four_mut(&mut self) -> (&mut [Limb], &mut [Limb], &mut [Limb], &mut [Limb]) {
split_into_chunks_mut!(self.data, self.s, [x00, x01, x10], x11);
(x00, x01, x10, x11)
}
pub_const_test! {min_init_scratch(n: usize) -> usize {
(((n + 1) >> 1) + 1) << 2
}}
pub_crate_test! {init(n: usize, p: &mut [Limb]) -> HalfGcdMatrix<'_> {
let s = n.div_ceil(2) + 1;
let two_s = s << 1;
let three_s = two_s + s;
slice_set_zero(&mut p[..s << 2]);
let mut m = HalfGcdMatrix {
data: p,
s,
two_s,
three_s,
n: 1,
};
m.get_mut(0, 0)[0] = 1;
m.get_mut(1, 1)[0] = 1;
m
}}
pub_const_test! {update_q_scratch_len(&self, qs_len: usize) -> usize {
self.n + qs_len
}}
fn all_elements_zero_at_index(&self, i: usize) -> bool {
self.get(0, 0)[i] == 0
&& self.get(0, 1)[i] == 0
&& self.get(1, 0)[i] == 0
&& self.get(1, 1)[i] == 0
}
}
pub_crate_test! {limbs_half_gcd_matrix_mul_matrix(
a: &mut HalfGcdMatrix,
b: &HalfGcdMatrix,
scratch: &mut [Limb]
) {
assert!(a.n + b.n < a.s);
assert!(!a.all_elements_zero_at_index(a.n - 1));
let b_n = b.n;
assert!(!b.all_elements_zero_at_index(b_n - 1));
let n = a.n;
let (x00, x01, x10, x11) = a.get_four_mut();
limbs_matrix_2_2_mul(
x00,
x01,
x10,
x11,
n,
&b.get(0, 0)[..b_n],
&b.get(0, 1)[..b_n],
&b.get(1, 0)[..b_n],
&b.get(1, 1)[..b_n],
scratch,
);
let mut n = a.n + b_n;
for _ in 0..3 {
if a.all_elements_zero_at_index(n) {
n -= 1;
}
}
assert!(!a.all_elements_zero_at_index(n));
a.n = n + 1;
}}
pub_crate_test! {limbs_half_gcd_matrix_mul_matrix_1(
a: &mut HalfGcdMatrix,
b: &HalfGcdMatrix1,
scratch: &mut [Limb],
) {
let n = a.n;
let scratch = &mut scratch[..n];
scratch.copy_from_slice(&a.get(0, 0)[..n]);
let (a_0_0, a_0_1) = a.get_two_mut(0, 0, 0, 1);
let n0 = limbs_half_gcd_matrix_1_mul_vector(b, a_0_0, scratch, a_0_1);
scratch.copy_from_slice(&a.get(1, 0)[..n]);
let (a_1_0, a_1_1) = a.get_two_mut(1, 0, 1, 1);
let n1 = limbs_half_gcd_matrix_1_mul_vector(b, a_1_0, scratch, a_1_1);
a.n = max(n0, n1);
assert!(a.n <= a.s);
}}
pub_crate_test! {limbs_half_gcd_matrix_update_q(
m: &mut HalfGcdMatrix,
qs: &[Limb],
column: u8,
scratch: &mut [Limb],
) {
let qs_len = qs.len();
assert!(qs_len + m.n <= m.s);
assert!(column < 2);
if qs_len == 1 {
let q = qs[0];
let n = m.n;
let (m_0_a, m_0_b) = m.get_two_mut(0, column, 0, 1 - column);
let carry_0 =
limbs_slice_add_mul_limb_same_length_in_place_left(&mut m_0_a[..n], &m_0_b[..n], q);
let (m_1_a, m_1_b) = m.get_two_mut(1, column, 1, 1 - column);
let carry_1 =
limbs_slice_add_mul_limb_same_length_in_place_left(&mut m_1_a[..n], &m_1_b[..n], q);
m.get_mut(0, column)[n] = carry_0;
m.get_mut(1, column)[n] = carry_1;
if carry_0 != 0 || carry_1 != 0 {
m.n += 1;
}
} else {
let mut carries = [0; 2];
let mut n = m.n;
while n + qs_len > m.n {
assert_ne!(n, 0);
if m.get(0, 1 - column)[n - 1] > 0 || m.get(1, 1 - column)[n - 1] > 0 {
break;
}
n -= 1;
}
assert!(qs_len + n <= m.s);
if n != 0 {
let mut mul_scratch = vec![0; limbs_mul_to_out_scratch_len(n, qs_len)];
for row in 0..2 {
limbs_mul_to_out(
scratch,
&m.get(row, 1 - column)[..n],
&qs[..qs_len],
&mut mul_scratch,
);
assert!(n + qs_len >= m.n);
let m_n = m.n;
if limbs_add_to_out_aliased(m.get_mut(row, column), m_n, &scratch[..n + qs_len]) {
carries[usize::wrapping_from(row)] = 1;
}
}
}
n += qs_len;
if carries[0] != 0 || carries[1] != 0 {
m.get_mut(0, column)[n] = carries[0];
m.get_mut(1, column)[n] = carries[1];
n += 1;
} else if m.get(0, column)[n - 1] == 0 && m.get(1, column)[n - 1] == 0 {
n -= 1;
}
m.n = n;
}
assert!(m.n <= m.s);
}}
pub(crate) fn limbs_half_gcd_matrix_adjust(
m: &HalfGcdMatrix,
mut n: usize,
xs: &mut [Limb],
ys: &mut [Limb],
p: usize,
scratch: &mut [Limb],
) -> usize {
let xs_init = &mut xs[..n];
let ys_init = &mut ys[..n];
let (scratch_lo, scratch_hi) = scratch.split_at_mut(p + m.n);
assert!(p + m.n < n);
let (xs_lo, xs_hi) = xs_init.split_at_mut(p);
let mut mul_scratch = vec![0; limbs_mul_to_out_scratch_len(m.n, p)];
limbs_mul_to_out(scratch_lo, &m.get(1, 1)[..m.n], xs_lo, &mut mul_scratch);
limbs_mul_to_out(scratch_hi, &m.get(1, 0)[..m.n], xs_lo, &mut mul_scratch);
let (scratch_lo_lo, scratch_lo_hi) = scratch_lo.split_at(p);
xs_lo.copy_from_slice(scratch_lo_lo);
let mut x_high = limbs_slice_add_greater_in_place_left(xs_hi, scratch_lo_hi);
let (ys_lo, ys_hi) = ys_init.split_at_mut(p);
limbs_mul_to_out(scratch_lo, &m.get(0, 1)[..m.n], ys_lo, &mut mul_scratch);
if limbs_sub_greater_in_place_left(xs_init, scratch_lo) {
assert!(x_high);
x_high = false;
}
limbs_mul_to_out(scratch_lo, &m.get(0, 0)[..m.n], ys_lo, &mut mul_scratch);
let (scratch_lo_lo, scratch_lo_hi) = scratch_lo.split_at(p);
ys_lo.copy_from_slice(scratch_lo_lo);
let mut y_high = limbs_slice_add_greater_in_place_left(ys_hi, scratch_lo_hi);
if limbs_sub_greater_in_place_left(ys_init, &scratch_hi[..p + m.n]) {
assert!(y_high);
y_high = false;
}
if x_high || y_high {
xs[n] = Limb::from(x_high);
ys[n] = Limb::from(y_high);
n += 1;
} else {
if xs[n - 1] == 0 && ys[n - 1] == 0 {
n -= 1;
}
}
assert!(xs[n - 1] != 0 || ys[n - 1] != 0);
n
}
fn limbs_half_gcd_matrix_apply(m: &HalfGcdMatrix, xs: &mut [Limb], ys: &mut [Limb]) -> usize {
let mut n = xs.len();
assert_eq!(ys.len(), n);
assert!(xs[n - 1] != 0 || ys[n - 1] != 0);
let xs_len = n - slice_trailing_zeros(&xs[..n]);
let ys_len = n - slice_trailing_zeros(&ys[..n]);
let xs_lo = &mut xs[..xs_len];
let ys_lo = &mut ys[..ys_len];
let mut m_lens = [[0usize; 2]; 2];
for (i, row) in m_lens.iter_mut().enumerate() {
for (j, len) in row.iter_mut().enumerate() {
*len = m.n - slice_trailing_zeros(&m.get(i as u8, j as u8)[..m.n]);
}
}
assert_ne!(m_lens[0][0], 0);
assert_ne!(m_lens[1][1], 0);
assert!(m_lens[0][1] != 0 || m_lens[1][0] != 0);
if m_lens[0][1] == 0 {
assert_eq!(m_lens[0][0], 1);
assert_eq!(m.get(0, 0)[0], 1);
assert_eq!(m_lens[1][1], 1);
assert_eq!(m.get(1, 1)[0], 1);
limbs_gcd_sub_mul(ys_lo, xs_lo, &m.get(1, 0)[..m_lens[1][0]])
} else if m_lens[1][0] == 0 {
fail_on_untested_path("limbs_half_gcd_matrix_apply, m_lens[1][0] == 0");
assert_eq!(m_lens[0][0], 1);
assert_eq!(m.get(0, 0)[0], 1);
assert_eq!(m_lens[1][1], 1);
assert_eq!(m.get(1, 1)[0], 1);
limbs_gcd_sub_mul(xs_lo, ys_lo, &m.get(0, 1)[..m_lens[0][1]])
} else {
let mut new_n = max(
min(xs_len - m_lens[0][0], ys_len - m_lens[1][0]),
min(xs_len - m_lens[0][1], ys_len - m_lens[1][1]),
) + 1;
let mod_n = limbs_mul_mod_base_pow_n_minus_1_next_size(new_n + 1);
let mut big_scratch =
vec![0; (mod_n << 1) + limbs_mul_mod_base_pow_n_minus_1_scratch_len(mod_n, mod_n, m.n)];
split_into_chunks_mut!(big_scratch, mod_n, [scratch, scratch_lo], scratch_hi);
assert!(n <= mod_n << 1);
if n > mod_n {
let (xs_lo, xs_hi) = xs.split_at_mut(mod_n);
if limbs_slice_add_greater_in_place_left(xs_lo, xs_hi) {
assert!(!limbs_slice_add_limb_in_place(xs, 1));
}
let (ys_lo, ys_hi) = ys.split_at_mut(mod_n);
if limbs_slice_add_greater_in_place_left(ys_lo, ys_hi) {
assert!(!limbs_slice_add_limb_in_place(ys, 1));
}
n = mod_n;
}
let xs = &mut xs[..n];
let ys = &mut ys[..n];
limbs_mul_mod_base_pow_n_minus_1(
scratch,
mod_n,
xs,
&m.get(1, 1)[..m_lens[1][1]],
scratch_hi,
);
limbs_mul_mod_base_pow_n_minus_1(
scratch_lo,
mod_n,
ys,
&m.get(0, 1)[..m_lens[0][1]],
scratch_hi,
);
if n + m_lens[1][1] < mod_n {
slice_set_zero(&mut scratch[n + m_lens[1][1]..]);
}
if n + m_lens[0][1] < mod_n {
slice_set_zero(&mut scratch_lo[n + m_lens[0][1]..]);
}
if limbs_sub_same_length_in_place_left(scratch, scratch_lo) {
assert!(!limbs_sub_limb_in_place(scratch, 1));
}
let (scratch_0, scratch_1) = scratch.split_at(new_n);
assert!(slice_test_zero(scratch_1));
limbs_mul_mod_base_pow_n_minus_1(
scratch_lo,
mod_n,
xs,
&m.get(1, 0)[..m_lens[1][0]],
scratch_hi,
);
xs[..new_n].copy_from_slice(scratch_0);
limbs_mul_mod_base_pow_n_minus_1(
scratch,
mod_n,
ys,
&m.get(0, 0)[..m_lens[0][0]],
scratch_hi,
);
if n + m_lens[1][0] < mod_n {
slice_set_zero(&mut scratch_lo[n + m_lens[1][0]..]);
}
if n + m_lens[0][0] < mod_n {
slice_set_zero(&mut scratch[n + m_lens[0][0]..]);
}
if limbs_sub_same_length_in_place_left(scratch, scratch_lo) {
assert!(!limbs_sub_limb_in_place(scratch, 1));
}
let (scratch_0, scratch_1) = scratch.split_at(new_n);
assert!(slice_test_zero(scratch_1));
ys[..new_n].copy_from_slice(scratch_0);
while xs[new_n - 1] | ys[new_n - 1] == 0 {
new_n -= 1;
assert_ne!(new_n, 0);
}
new_n
}
}
fn limbs_half_gcd_matrix_reduce(
m: &mut HalfGcdMatrix,
xs: &mut [Limb],
ys: &mut [Limb],
p: usize,
scratch: &mut [Limb],
) -> usize {
let n = xs.len();
assert_eq!(ys.len(), n);
let com_p = n - p;
if n < HGCD_REDUCE_THRESHOLD {
let new_n = limbs_half_gcd(&mut xs[p..], &mut ys[p..], m, scratch);
if new_n == 0 {
0
} else {
limbs_half_gcd_matrix_adjust(m, p + new_n, xs, ys, p, scratch)
}
} else {
split_into_chunks_mut!(scratch, com_p, [scratch_0, scratch_1], scratch_2);
scratch_0.copy_from_slice(&xs[p..]);
scratch_1.copy_from_slice(&ys[p..]);
if limbs_half_gcd_approx(scratch_0, scratch_1, m, scratch_2) {
limbs_half_gcd_matrix_apply(m, xs, ys)
} else {
0
}
}
}
fn limbs_gcd_sub_mul(out: &mut [Limb], xs: &[Limb], ys: &[Limb]) -> usize {
let mut out_len = out.len();
let xs_len = xs.len();
let ys_len = ys.len();
assert_ne!(ys_len, 0);
assert!(xs_len >= ys_len);
assert!(out_len >= xs_len);
let sum_len = xs_len + ys_len;
assert!(sum_len <= out_len + 1);
let mut scratch = vec![0; sum_len];
let mut mul_scratch = vec![0; limbs_mul_greater_to_out_scratch_len(xs_len, ys_len)];
limbs_mul_greater_to_out(&mut scratch, xs, ys, &mut mul_scratch);
assert!(sum_len <= out_len || scratch[out_len] == 0);
let mut scratch_len = sum_len;
if scratch_len > out_len {
scratch_len -= 1;
}
assert!(!limbs_sub_greater_in_place_left(
out,
&scratch[..scratch_len]
));
while out_len > xs_len && out[out_len - 1] == 0 {
out_len -= 1;
}
out_len
}
#[cfg(feature = "test_build")]
#[derive(Clone, Debug, Default)]
pub struct HalfGcdMatrix1 {
pub data: [[Limb; 2]; 2],
}
#[cfg(not(feature = "test_build"))]
#[derive(Default)]
pub(crate) struct HalfGcdMatrix1 {
pub(crate) data: [[Limb; 2]; 2],
}
pub_crate_test! {limbs_half_gcd_matrix_1_mul_vector(
m: &HalfGcdMatrix1,
out: &mut [Limb],
xs: &[Limb],
ys: &mut [Limb],
) -> usize {
let n = xs.len();
assert!(ys.len() > n);
assert!(out.len() > n);
let (out_lo, out_hi) = out.split_at_mut(n);
let (ys_lo, ys_hi) = ys.split_at_mut(n);
let mut x_high = limbs_mul_limb_to_out::<DoubleLimb, Limb>(out_lo, xs, m.data[0][0]);
x_high.wrapping_add_assign(
limbs_slice_add_mul_limb_same_length_in_place_left(out_lo, ys_lo, m.data[1][0])
);
let mut y_high = limbs_slice_mul_limb_in_place(ys_lo, m.data[1][1]);
y_high.wrapping_add_assign(
limbs_slice_add_mul_limb_same_length_in_place_left(ys_lo, xs, m.data[0][1])
);
out_hi[0] = x_high;
ys_hi[0] = y_high;
if x_high == 0 && y_high == 0 {
n
} else {
n + 1
}
}}
pub(crate) fn limbs_half_gcd_matrix_1_mul_inverse_vector(
m: &HalfGcdMatrix1,
out: &mut [Limb],
xs: &[Limb],
ys: &mut [Limb],
) -> usize {
let n = xs.len();
assert_eq!(ys.len(), n);
assert_eq!(out.len(), n);
let h0 = limbs_mul_limb_to_out::<DoubleLimb, Limb>(out, xs, m.data[1][1]);
let h1 = limbs_sub_mul_limb_same_length_in_place_left(out, ys, m.data[0][1]);
assert_eq!(h0, h1);
let h0 = limbs_slice_mul_limb_in_place(ys, m.data[0][0]);
let h1 = limbs_sub_mul_limb_same_length_in_place_left(ys, xs, m.data[1][0]);
assert_eq!(h0, h1);
if out[n - 1] == 0 && ys[n - 1] == 0 {
n - 1
} else {
n
}
}
pub(crate) fn limbs_gcd_subdivide_step<'a, CTX: GcdSubdivideStepContext>(
mut xs: &'a mut [Limb],
mut ys: &'a mut [Limb],
s: usize,
context: &mut CTX,
scratch: &mut [Limb],
) -> usize {
let n = xs.len();
assert_eq!(ys.len(), n);
assert_ne!(n, 0);
assert!(xs[n - 1] > 0 || ys[n - 1] > 0);
let mut xs_len = n - slice_trailing_zeros(xs);
let mut ys_len = n - slice_trailing_zeros(ys);
let mut xs_init = &mut xs[..xs_len];
let mut ys_init = &mut ys[..ys_len];
let mut swapped = false;
match xs_len.cmp(&ys_len) {
Equal => {
match limbs_cmp_same_length(xs_init, ys_init) {
Equal => {
if s == 0 {
context.gcd_subdiv_step_hook(Some(xs_init), None, 0, -1);
}
return 0;
}
Greater => {
swap(&mut xs, &mut ys);
xs_init = &mut xs[..xs_len];
ys_init = &mut ys[..ys_len];
swapped.not_assign();
}
_ => {}
}
}
Greater => {
swap(&mut xs, &mut ys);
swap(&mut xs_len, &mut ys_len);
xs_init = &mut xs[..xs_len];
ys_init = &mut ys[..ys_len];
swapped.not_assign();
}
Less => {}
}
if xs_len <= s {
if s == 0 {
context.gcd_subdiv_step_hook(Some(ys_init), None, 0, i8::from(!swapped));
}
return 0;
}
assert!(!limbs_sub_greater_in_place_left(ys_init, xs_init));
ys_len -= slice_trailing_zeros(ys_init);
ys_init = &mut ys_init[..ys_len];
assert_ne!(ys_len, 0);
if ys_len <= s {
if limbs_add_to_out_aliased(ys, ys_len, xs_init) {
ys[xs_len] = 1;
}
return 0;
}
match xs_len.cmp(&ys_len) {
Equal => {
match limbs_cmp_same_length(xs_init, ys_init) {
Equal => {
fail_on_untested_path("limbs_gcd_subdivide_step, c == Equal");
if s != 0 {
context.gcd_subdiv_step_hook_with_1(i8::from(swapped));
context.gcd_subdiv_step_hook_with_1(i8::from(swapped));
} else {
context.gcd_subdiv_step_hook(Some(ys_init), None, 0, i8::from(swapped));
return 0;
}
}
Greater => {
context.gcd_subdiv_step_hook_with_1(i8::from(swapped));
swap(&mut xs, &mut ys);
xs_init = &mut xs[..xs_len];
ys_init = &mut ys[..ys_len];
swapped.not_assign();
}
Less => {
context.gcd_subdiv_step_hook_with_1(i8::from(swapped));
}
}
}
Greater => {
context.gcd_subdiv_step_hook_with_1(i8::from(swapped));
swap(&mut xs, &mut ys);
swap(&mut xs_len, &mut ys_len);
xs_init = &mut xs[..xs_len];
ys_init = &mut ys[..ys_len];
swapped.not_assign();
}
Less => {
context.gcd_subdiv_step_hook_with_1(i8::from(swapped));
}
}
if xs_len == 1 {
if ys_init.len() == 1 {
(scratch[0], ys_init[0]) = ys_init[0].div_mod(xs_init[0]);
} else {
ys_init[0] = limbs_div_limb_to_out_mod(scratch, ys_init, xs_init[0]);
}
} else {
limbs_div_mod_qs_to_out_rs_to_ns(scratch, ys_init, xs_init);
}
let qn = ys_len - xs_len + 1;
let ys_len = xs_len - slice_trailing_zeros(&ys_init[..xs_len]);
if ys_len <= s {
if s == 0 {
context.gcd_subdiv_step_hook(Some(xs_init), Some(scratch), qn, i8::from(swapped));
return 0;
}
if ys_len != 0 {
if limbs_add_to_out_aliased(ys, ys_len, xs_init) {
ys[xs_len] = 1;
xs_len += 1;
}
} else {
ys[..xs_len].copy_from_slice(xs_init);
}
assert!(!limbs_sub_limb_in_place(&mut scratch[..qn], 1));
}
context.gcd_subdiv_step_hook(None, Some(scratch), qn, i8::from(swapped));
xs_len
}
impl GcdSubdivideStepContext for HalfGcdMatrix<'_> {
fn gcd_subdiv_step_hook(
&mut self,
g: Option<&[Limb]>,
q: Option<&mut [Limb]>,
mut q_len: usize,
d: i8,
) {
assert!(g.is_none());
let q = q.unwrap();
q_len -= slice_trailing_zeros(&q[..q_len]);
if q_len != 0 {
let (q, scratch) = q.split_at_mut(q_len);
limbs_half_gcd_matrix_update_q(self, q, u8::exact_from(d), scratch);
}
}
}
pub(crate) const fn extract_number(count: u64, x1: Limb, x0: Limb) -> Limb {
(x1 << count) | (x0 >> (Limb::WIDTH - count))
}
pub_crate_test! {limbs_gcd_div(
mut n1: Limb,
mut n0: Limb,
mut d1: Limb,
mut d0: Limb
) -> (Limb, Limb, Limb) {
let (mut q, r) = n1.div_mod(d1);
if q > d1 {
let c = LeadingZeros::leading_zeros(d1);
let width_comp = Limb::WIDTH - c;
assert_ne!(c, 0);
let n2 = n1 >> width_comp;
n1 = (n1 << c) | (n0 >> width_comp);
n0 <<= c;
d1 = (d1 << c) | (d0 >> width_comp);
d0 <<= c;
(q, n1) = Limb::xx_div_mod_y_to_qr(n2, n1, d1);
let (mut t1, mut t0) = Limb::x_mul_y_to_zz(q, d0);
if t1 > n1 || t1 == n1 && t0 > n0 {
assert_ne!(q, 0);
q -= 1;
(t1, t0) = Limb::xx_sub_yy_to_zz(t1, t0, d1, d0);
}
(n1, n0) = Limb::xx_sub_yy_to_zz(n1, n0, t1, t0);
(q, n1 >> c, (n0 >> c) | (n1 << width_comp))
} else {
n1 = r;
let (mut t1, mut t0) = Limb::x_mul_y_to_zz(q, d0);
if t1 >= n1 && (t1 > n1 || t0 > n0) {
assert_ne!(q, 0);
q -= 1;
(t1, t0) = Limb::xx_sub_yy_to_zz(t1, t0, d1, d0);
}
let (r1, r0) = Limb::xx_sub_yy_to_zz(n1, n0, t1, t0);
(q, r1, r0)
}
}}
pub(crate) fn limbs_half_gcd_2(
mut x_high: Limb,
mut a_low: Limb,
mut y_high: Limb,
mut b_low: Limb,
m: &mut HalfGcdMatrix1,
) -> bool {
if x_high < 2 || y_high < 2 {
return false;
}
let mut m01;
let mut m10;
if x_high > y_high || x_high == y_high && a_low > b_low {
(x_high, a_low) = Limb::xx_sub_yy_to_zz(x_high, a_low, y_high, b_low);
if x_high < 2 {
return false;
}
m01 = 1;
m10 = 0;
} else {
(y_high, b_low) = Limb::xx_sub_yy_to_zz(y_high, b_low, x_high, a_low);
if y_high < 2 {
return false;
}
m01 = 0;
m10 = 1;
}
let mut m00 = 1;
let mut m11 = 1;
const HALF_WIDTH: u64 = Limb::WIDTH >> 1;
const HALF_LIMIT_1: Limb = 1 << HALF_WIDTH;
let mut subtract_a = x_high < y_high;
let mut subtract_a1 = false;
let mut done = false;
loop {
if subtract_a {
subtract_a = false;
} else {
assert!(x_high >= y_high);
if x_high == y_high {
done = true;
break;
}
if x_high < HALF_LIMIT_1 {
x_high = (x_high << HALF_WIDTH) + (a_low >> HALF_WIDTH);
y_high = (y_high << HALF_WIDTH) + (b_low >> HALF_WIDTH);
break;
}
assert!(x_high > y_high);
(x_high, a_low) = Limb::xx_sub_yy_to_zz(x_high, a_low, y_high, b_low);
if x_high < 2 {
done = true;
break;
}
if x_high <= y_high {
m01 += m00;
m11 += m10;
} else {
let mut q;
(q, x_high, a_low) = limbs_gcd_div(x_high, a_low, y_high, b_low);
if x_high < 2 {
m01 += q * m00;
m11 += q * m10;
done = true;
break;
}
q += 1;
m01 += q * m00;
m11 += q * m10;
}
}
assert!(y_high >= x_high);
if x_high == y_high {
done = true;
break;
}
if y_high < HALF_LIMIT_1 {
x_high = (x_high << HALF_WIDTH) + (a_low >> HALF_WIDTH);
y_high = (y_high << HALF_WIDTH) + (b_low >> HALF_WIDTH);
subtract_a1 = true;
break;
}
(y_high, b_low) = Limb::xx_sub_yy_to_zz(y_high, b_low, x_high, a_low);
if y_high < 2 {
done = true;
break;
}
if x_high >= y_high {
m00 += m01;
m10 += m11;
} else {
let mut q;
(q, y_high, b_low) = limbs_gcd_div(y_high, b_low, x_high, a_low);
if y_high < 2 {
m00 += q * m01;
m10 += q * m11;
done = true;
break;
}
q += 1;
m00 += q * m01;
m10 += q * m11;
}
}
if !done {
const HALF_LIMIT_2: Limb = 1 << (HALF_WIDTH + 1);
loop {
if subtract_a1 {
subtract_a1 = false;
} else {
assert!(x_high >= y_high);
x_high -= y_high;
if x_high < HALF_LIMIT_2 {
break;
}
if x_high <= y_high {
m01 += m00;
m11 += m10;
} else {
let mut q;
(q, x_high) = x_high.div_mod(y_high);
if x_high < HALF_LIMIT_2 {
m01 += q * m00;
m11 += q * m10;
break;
}
q += 1;
m01 += q * m00;
m11 += q * m10;
}
}
assert!(y_high >= x_high);
y_high -= x_high;
if y_high < HALF_LIMIT_2 {
break;
}
if x_high >= y_high {
m00 += m01;
m10 += m11;
} else {
let mut q;
(q, y_high) = y_high.div_mod(x_high);
if y_high < HALF_LIMIT_2 {
m00 += q * m01;
m10 += q * m11;
break;
}
q += 1;
m00 += q * m01;
m10 += q * m11;
}
}
}
m.data[0][0] = m00;
m.data[0][1] = m01;
m.data[1][0] = m10;
m.data[1][1] = m11;
true
}
fn limbs_half_gcd_step(
xs: &mut [Limb],
ys: &mut [Limb],
s: usize,
a: &mut HalfGcdMatrix,
scratch: &mut [Limb],
) -> usize {
let n = xs.len();
assert_eq!(ys.len(), n);
assert!(n > s);
let mask = xs[n - 1] | ys[n - 1];
assert_ne!(mask, 0);
let (x_high, a_low, y_high, b_low) = if n == s + 1 {
if mask < 4 {
return limbs_gcd_subdivide_step(xs, ys, s, a, scratch);
}
(xs[n - 1], xs[n - 2], ys[n - 1], ys[n - 2])
} else if mask.get_highest_bit() {
(xs[n - 1], xs[n - 2], ys[n - 1], ys[n - 2])
} else {
let shift = LeadingZeros::leading_zeros(mask);
(
extract_number(shift, xs[n - 1], xs[n - 2]),
extract_number(shift, xs[n - 2], xs[n - 3]),
extract_number(shift, ys[n - 1], ys[n - 2]),
extract_number(shift, ys[n - 2], ys[n - 3]),
)
};
let mut b = HalfGcdMatrix1::default();
if limbs_half_gcd_2(x_high, a_low, y_high, b_low, &mut b) {
limbs_half_gcd_matrix_mul_matrix_1(a, &b, scratch);
let scratch = &mut scratch[..n];
scratch.copy_from_slice(xs);
limbs_half_gcd_matrix_1_mul_inverse_vector(&b, xs, scratch, ys)
} else {
limbs_gcd_subdivide_step(xs, ys, s, a, scratch)
}
}
pub(crate) const fn limbs_gcd_subdivide_step_scratch_len(n: usize) -> usize {
n
}
const fn limbs_gcd_choose_p(n: usize) -> usize {
(n << 1) / 3
}
pub(crate) const fn limbs_half_gcd_matrix_init_scratch_len(n: usize) -> usize {
(((n + 1) >> 1) + 1) << 2
}
pub(crate) const HGCD_THRESHOLD: usize = 101;
pub(crate) fn limbs_half_gcd_scratch_len(n: usize) -> usize {
if n < HGCD_THRESHOLD {
n
} else {
let count = LeadingZeros::leading_zeros((n - 1) / (HGCD_THRESHOLD - 1));
20 * ((n + 3) >> 2) + 22 * usize::exact_from(usize::WIDTH - count) + HGCD_THRESHOLD
}
}
const HGCD_REDUCE_THRESHOLD: usize = 1679;
pub_test! {limbs_half_gcd_reduce_scratch_len(n: usize, p: usize) -> usize {
assert!(n >= p);
let diff = n - p;
if n < HGCD_REDUCE_THRESHOLD {
let scratch_len = limbs_half_gcd_scratch_len(diff);
let sum = n + p - 1;
if scratch_len < sum {
sum
} else {
scratch_len
}
} else {
(diff << 1) + limbs_half_gcd_scratch_len(diff)
}
}}
const HGCD_APPR_THRESHOLD: usize = 104;
fn limbs_half_gcd_approx(
mut xs: &mut [Limb],
mut ys: &mut [Limb],
a: &mut HalfGcdMatrix,
scratch: &mut [Limb],
) -> bool {
let mut n = xs.len();
assert_eq!(ys.len(), n);
assert_ne!(n, 0);
assert!(xs[n - 1] != 0 || ys[n - 1] != 0);
if n <= 2 {
fail_on_untested_path("limbs_half_gcd_approx, n <= 2");
return false;
}
assert!(((n + 1) >> 1) - 1 < a.s);
let mut s = (n >> 1) + 1;
let mut offset = 0;
let mut success = false;
if n < HGCD_APPR_THRESHOLD {
let mut extra_bits = 0u64;
let mut xs_chunk = &mut *xs;
let mut ys_chunk = &mut *ys;
while n > 2 {
assert!(n > s);
assert!(n <= s << 1);
let new_n = limbs_half_gcd_step(xs_chunk, ys_chunk, s, a, scratch);
if new_n == 0 {
break;
}
n = new_n;
xs_chunk = &mut xs_chunk[..n];
ys_chunk = &mut ys_chunk[..n];
success = true;
if limb_to_bit_count(n + 1) + (extra_bits << 1) <= limb_to_bit_count(s << 1) {
let p =
bit_to_limb_count_floor(limb_to_bit_count((s << 1) - n) - (extra_bits << 1));
if extra_bits == 0 {
if s + 1 == n
|| slice_test_zero(&xs_chunk[s + 1..])
|| slice_test_zero(&ys_chunk[s + 1..])
{
continue;
}
extra_bits = Limb::WIDTH - 1;
s += 1;
} else {
extra_bits -= 1;
}
offset += p;
xs_chunk = &mut xs_chunk[p..];
ys_chunk = &mut ys_chunk[p..];
n -= p;
s -= p;
}
}
assert_ne!(s, 0);
if extra_bits != 0 {
assert_ne!(offset, 0);
let xs = &mut xs[offset - 1..];
let ys = &mut ys[offset - 1..];
let (xs_head, xs_tail) = xs[..=n].split_first_mut().unwrap();
let (ys_head, ys_tail) = ys[..=n].split_first_mut().unwrap();
let comp_bits = Limb::WIDTH - extra_bits;
*xs_head = limbs_slice_shr_in_place(xs_tail, comp_bits);
*ys_head = limbs_slice_shr_in_place(ys_tail, comp_bits);
if xs[n] != 0 || ys[n] != 0 {
n += 1;
}
assert!(success);
while n > 2 {
assert!(n > s);
assert!(n <= s << 1);
n = limbs_half_gcd_step(&mut xs[..n], &mut ys[..n], s, a, scratch);
if n == 0 {
return true;
}
}
}
if n == 2 {
fail_on_untested_path("limbs_half_gcd_approx, n == 2");
assert_eq!(s, 1);
let mut b = HalfGcdMatrix1::default();
if limbs_half_gcd_2(xs[1], xs[0], ys[1], ys[0], &mut b) {
limbs_half_gcd_matrix_mul_matrix_1(a, &b, scratch);
success = true;
}
}
success
} else {
let limit = ((3 * n) >> 2) + 1;
let mut p = n >> 1;
let new_n = limbs_half_gcd_matrix_reduce(a, xs, ys, p, scratch);
if new_n != 0 {
n = new_n;
xs = &mut xs[..n];
ys = &mut ys[..n];
success = true;
}
while n > limit {
n = limbs_half_gcd_step(xs, ys, s, a, scratch);
if n == 0 {
return success;
}
xs = &mut xs[..n];
ys = &mut ys[..n];
success = true;
}
if n > s + 2 {
p = (s << 1) - n + 1;
let scratch_len = limbs_half_gcd_matrix_init_scratch_len(n - p);
let (scratch_lo, scratch_hi) = scratch.split_at_mut(scratch_len);
let mut b = HalfGcdMatrix::init(n - p, scratch_lo);
if limbs_half_gcd_approx(&mut xs[p..], &mut ys[p..], &mut b, scratch_hi) {
assert!(a.n + 2 >= b.n);
assert!(a.n + b.n < a.s);
limbs_half_gcd_matrix_mul_matrix(a, &b, scratch_hi);
return true;
}
}
loop {
assert!(n > s);
assert!(n <= s << 1);
let new_n = limbs_half_gcd_step(xs, ys, s, a, scratch);
if new_n == 0 {
return success;
}
n = new_n;
xs = &mut xs[..n];
ys = &mut ys[..n];
success = true;
}
}
}
pub(crate) fn limbs_half_gcd(
xs: &mut [Limb],
ys: &mut [Limb],
a: &mut HalfGcdMatrix,
scratch: &mut [Limb],
) -> usize {
let mut n = xs.len();
assert_eq!(ys.len(), n);
let s = (n >> 1) + 1;
let mut success = false;
if n <= s {
fail_on_untested_path("limbs_half_gcd, n <= s");
return 0;
}
assert!(xs[n - 1] != 0 || ys[n - 1] != 0);
assert!(((n + 1) >> 1) - 1 < a.s);
if n >= HGCD_THRESHOLD {
let limit = ((3 * n) >> 2) + 1;
let p = n >> 1;
let mut new_n = limbs_half_gcd_matrix_reduce(a, xs, ys, p, scratch);
if new_n != 0 {
n = new_n;
success = true;
}
while n > limit {
let new_n = limbs_half_gcd_step(&mut xs[..n], &mut ys[..n], s, a, scratch);
if new_n == 0 {
return if success { n } else { 0 };
}
n = new_n;
success = true;
}
if n > s + 2 {
let p = (s << 1) - n + 1;
let scratch_len = limbs_half_gcd_matrix_init_scratch_len(n - p);
let (scratch_lo, scratch_hi) = scratch.split_at_mut(scratch_len);
let mut b = HalfGcdMatrix::init(n - p, scratch_lo);
new_n = limbs_half_gcd(&mut xs[p..n], &mut ys[p..n], &mut b, scratch_hi);
if new_n != 0 {
assert!(a.n + 2 >= b.n);
assert!(a.n + b.n < a.s);
n = limbs_half_gcd_matrix_adjust(&b, p + new_n, xs, ys, p, scratch_hi);
limbs_half_gcd_matrix_mul_matrix(a, &b, scratch_hi);
success = true;
}
}
}
loop {
let new_n = limbs_half_gcd_step(&mut xs[..n], &mut ys[..n], s, a, scratch);
if new_n == 0 {
return if success { n } else { 0 };
}
n = new_n;
success = true;
}
}
pub(crate) const GCD_DC_THRESHOLD: usize = 330;
pub_crate_test! {limbs_gcd_reduced(out: &mut [Limb], xs: &mut [Limb], ys: &mut [Limb]) -> usize {
let mut xs = &mut *xs;
let mut ys = &mut *ys;
let xs_len = xs.len();
let mut n = ys.len();
assert!(xs_len >= n);
assert_ne!(n, 0);
assert_ne!(ys[n - 1], 0);
let mut scratch_len = max(xs_len - n + 1, limbs_gcd_subdivide_step_scratch_len(n));
if n >= GCD_DC_THRESHOLD {
let p = limbs_gcd_choose_p(n);
let matrix_scratch_len = limbs_half_gcd_matrix_init_scratch_len(n - p);
let half_gcd_scratch_len = limbs_half_gcd_scratch_len(n - p);
let update_scratch_len = p + n - 1;
scratch_len = max(
scratch_len,
matrix_scratch_len + max(half_gcd_scratch_len, update_scratch_len),
);
}
let mut scratch = vec![0; scratch_len];
let mut scratch = &mut scratch[..];
if xs_len > n {
limbs_div_mod_qs_to_out_rs_to_ns(scratch, xs, ys);
if slice_test_zero(&xs[..n]) {
out[..n].copy_from_slice(ys);
return n;
}
}
while n >= GCD_DC_THRESHOLD {
let xs = &mut xs[..n];
let ys = &mut ys[..n];
let p = limbs_gcd_choose_p(n);
let comp_p = n - p;
let matrix_scratch_len = limbs_half_gcd_matrix_init_scratch_len(comp_p);
let (scratch_lo, scratch_hi) = scratch.split_at_mut(matrix_scratch_len);
let mut m = HalfGcdMatrix::init(comp_p, scratch_lo);
let new_n = limbs_half_gcd(&mut xs[p..], &mut ys[p..], &mut m, scratch_hi);
if new_n != 0 {
assert!(m.n <= (comp_p - 1) >> 1);
assert!(m.n + p <= (p + n - 1) >> 1);
n = limbs_half_gcd_matrix_adjust(&m, p + new_n, xs, ys, p, scratch_hi);
} else {
let out_len = n;
n = limbs_gcd_subdivide_step(xs, ys, 0, &mut GcdContext(out), scratch);
if n == 0 {
return out_len;
}
}
}
while n > 2 {
let mask = xs[n - 1] | ys[n - 1];
assert_ne!(mask, 0);
let (x_hi, x_lo, y_hi, y_lo) = if mask.get_highest_bit() {
(xs[n - 1], xs[n - 2], ys[n - 1], ys[n - 2])
} else {
let shift = LeadingZeros::leading_zeros(mask);
(
extract_number(shift, xs[n - 1], xs[n - 2]),
extract_number(shift, xs[n - 2], xs[n - 3]),
extract_number(shift, ys[n - 1], ys[n - 2]),
extract_number(shift, ys[n - 2], ys[n - 3]),
)
};
let mut m = HalfGcdMatrix1::default();
if limbs_half_gcd_2(x_hi, x_lo, y_hi, y_lo, &mut m) {
n = limbs_half_gcd_matrix_1_mul_inverse_vector(
&m,
&mut scratch[..n],
&xs[..n],
&mut ys[..n],
);
swap(&mut xs, &mut scratch);
} else {
let out_len = n;
n = limbs_gcd_subdivide_step(
&mut xs[..n],
&mut ys[..n],
0,
&mut GcdContext(out),
scratch,
);
if n == 0 {
return out_len;
}
}
}
assert!(xs[n - 1] != 0 || ys[n - 1] != 0);
if xs[0].even() {
swap(&mut xs, &mut ys);
}
assert!(xs[0].odd());
let x_0 = xs[0];
let mut y_0 = ys[0];
if n == 1 {
out[0] = x_0.gcd(y_0 >> y_0.trailing_zeros());
return 1;
}
let mut y_1 = ys[1];
if y_0 == 0 {
y_0 = y_1;
y_1 = 0;
}
if y_0.even() {
let zeros = TrailingZeros::trailing_zeros(y_0);
y_0 = (y_1 << (Limb::WIDTH - zeros)) | (y_0 >> zeros);
y_1 >>= zeros;
}
let x_1 = xs[1];
(out[1], out[0]) = DoubleLimb::join_halves(x_1, x_0)
.gcd(DoubleLimb::join_halves(y_1, y_0))
.split_in_half();
if out[1] == 0 {
1
} else {
2
}
}}