use super::super::div::limbs::{
DC_DIV, bdiv_q, binvert_limb, div_qr, div_qr_1, div_qr_2, invert_3by2, invert_limb, qr_itch,
};
use super::super::mul::limbs::{add_1, add_n, cmp_n, mul, sub_1, sub_n};
use super::super::mul::{on_heap, on_stack};
use super::kernels::{addmul_1, extend_row, gcd_22, reduce_pair, small_q1, small_q2};
use core::cmp::Ordering;
use core::mem::MaybeUninit;
use core::ptr;
#[derive(Clone, Copy, Debug)]
pub(super) struct Matrix1 {
pub(super) u00: u64,
pub(super) u01: u64,
pub(super) u10: u64,
pub(super) u11: u64,
}
#[inline(always)]
fn join(h: u64, l: u64) -> u128 {
(u128::from(h) << 64) | u128::from(l)
}
#[inline(always)]
fn high(x: u128) -> u64 {
(x >> 64) as u64
}
#[inline(always)]
fn divmod2<const ONES: bool>(n: u128, d: u128) -> (u64, u128) {
if ONES {
if n < d {
return (0, n);
}
let t = n - d;
if t < d {
return (1, t);
}
}
if (n >> 3) < d && d >> 125 == 0 {
return small_q2(n, d);
}
long_q2(n, d)
}
#[inline(never)]
fn long_q2(mut n: u128, d: u128) -> (u64, u128) {
let k = d.leading_zeros().saturating_sub(n.leading_zeros());
let mut d = d << k;
let mut q = 0u64;
for _ in 0..=k {
let take = n >= d;
q = (q << 1) | u64::from(take);
n -= if take { d } else { 0 };
d >>= 1;
}
(q, n)
}
#[inline(always)]
fn divmod1<const ONES: bool>(n: u64, d: u64) -> (u64, u64) {
debug_assert!(n >= d);
if ONES {
let t = n - d;
if t < d {
return (1, t);
}
}
if (n >> 3) < d && d >> 61 == 0 {
return small_q1(n, d);
}
let q = n / d;
(q, n - q * d)
}
const LIMIT_2: u64 = 2;
const LIMIT_1: u64 = 1 << 33;
type Column = (u64, u64);
#[inline(always)]
fn plus_times(c: Column, q: u64, d: Column) -> Column {
(c.0 + q * d.0, c.1 + q * d.1)
}
pub(super) fn hgcd2(ah: u64, al: u64, bh: u64, bl: u64) -> Option<Matrix1> {
hgcd2_steps::<false>(ah, al, bh, bl).map(|(m, _)| m)
}
pub(super) const ONES_STEPS: u32 = 64;
#[inline(always)]
pub(super) fn hgcd2_steps<const ONES: bool>(
ah: u64,
al: u64,
bh: u64,
bl: u64,
) -> Option<(Matrix1, u32)> {
if ah < LIMIT_2 || bh < LIMIT_2 {
return None;
}
let (a, b) = (join(ah, al), join(bh, bl));
let (mut r0, mut r1, mut r0_is_a) = if a > b { (a, b, true) } else { (b, a, false) };
let (mut c0, mut c1): (Column, Column) = if r0_is_a {
((1, 0), (0, 1))
} else {
((0, 1), (1, 0))
};
let first = r0 - r1;
if high(first) < LIMIT_2 {
return None;
}
let (q, r) = divmod2::<ONES>(first, r1);
if high(r) < LIMIT_2 {
return Some((matrix1(c0, plus_times(c1, q, c0), r0_is_a), 1));
}
(c0, c1) = (plus_times(c1, q + 1, c0), c0);
(r0, r1) = (r1, r);
r0_is_a = !r0_is_a;
let mut steps = 1;
while high(r0) >= 1 << 32 {
let (q, r) = divmod2::<ONES>(r0, r1);
if high(r) < LIMIT_2 {
return Some((matrix1(c0, plus_times(c1, q - 1, c0), r0_is_a), steps + 1));
}
(c0, c1) = (plus_times(c1, q, c0), c0);
(r0, r1) = (r1, r);
r0_is_a = !r0_is_a;
steps += 1;
}
let (mut s0, mut s1) = ((r0 >> 32) as u64, (r1 >> 32) as u64);
loop {
let (q, r) = divmod1::<ONES>(s0, s1);
if r < LIMIT_1 {
return Some((matrix1(c0, plus_times(c1, q - 1, c0), r0_is_a), steps + 1));
}
(c0, c1) = (plus_times(c1, q, c0), c0);
(s0, s1) = (s1, r);
r0_is_a = !r0_is_a;
steps += 1;
}
}
#[inline(always)]
pub(super) fn hgcd2_hinted(ah: u64, al: u64, bh: u64, bl: u64, ones: &mut bool) -> Option<Matrix1> {
let found = if *ones {
hgcd2_steps::<true>(ah, al, bh, bl)
} else {
hgcd2_steps::<false>(ah, al, bh, bl)
};
found.map(|(m, steps)| {
*ones = steps >= ONES_STEPS;
m
})
}
#[inline(always)]
fn matrix1(c0: Column, c1: Column, r0_is_a: bool) -> Matrix1 {
let (ca, cb) = if r0_is_a { (c0, c1) } else { (c1, c0) };
Matrix1 {
u00: ca.0,
u01: cb.0,
u10: ca.1,
u11: cb.1,
}
}
#[inline]
pub(super) fn gcd_11(mut u: u64, mut v: u64) -> u64 {
debug_assert!(u & v & 1 == 1);
while u != v {
let (d, below) = u.overflowing_sub(v);
let zeros = d.trailing_zeros();
if below {
v = u;
}
u = if below { d.wrapping_neg() } else { d } >> zeros;
}
u
}
#[inline(always)]
pub(super) fn gcd_u128(x: u128, y: u128) -> u128 {
if (x | y) >> 64 == 0 {
return u128::from(gcd_u64(x as u64, y as u64));
}
gcd_wide(x, y)
}
#[inline(always)]
pub(super) fn gcd_u64(x: u64, y: u64) -> u64 {
if x == 0 || y == 0 {
return x | y;
}
let k = (x | y).trailing_zeros();
let (mut x, mut y) = (x >> x.trailing_zeros(), y >> y.trailing_zeros());
if x < y {
(x, y) = (y, x);
}
if x >> 16 > y {
x %= y;
if x == 0 {
return y << k;
}
x >>= x.trailing_zeros();
}
gcd_11(x, y) << k
}
#[inline(never)]
fn gcd_wide(x: u128, y: u128) -> u128 {
if x == 0 || y == 0 {
return x | y;
}
let k = (x | y).trailing_zeros();
let (x, y) = (x >> x.trailing_zeros(), y >> y.trailing_zeros());
let g = if (x | y) >> 64 == 0 {
u128::from(gcd_11(x as u64, y as u64))
} else {
gcd_22(x, y)
};
g << k
}
#[inline]
pub(super) fn gcdext_1(a: u64, b: u64) -> (u64, i64, i64) {
debug_assert!(a != 0 && b != 0);
if a < b {
let (g, v, u) = gcdext_1(b, a);
return (g, u, v);
}
let (mut r0, mut r1) = (a, b);
let (mut u0, mut v0, mut u1, mut v1) = (1i64, 0i64, 0i64, 1i64);
loop {
let (q, r) = divmod1::<false>(r0, r1);
if r == 0 {
return (r1, u1, v1);
}
let q = q as i64;
(u0, u1) = (u1, u0 - q * u1);
(v0, v1) = (v1, v0 - q * v1);
(r0, r1) = (r1, r);
}
}
#[inline(always)]
pub(super) unsafe fn normalized(p: *const u64, mut n: usize) -> usize {
unsafe {
while n > 0 && *p.add(n - 1) == 0 {
n -= 1;
}
}
n
}
#[inline]
pub(super) unsafe fn lshift(r: *mut u64, x: *const u64, n: usize, s: u32) -> u64 {
unsafe {
if s == 0 {
if r.cast_const() != x {
ptr::copy_nonoverlapping(x, r, n);
}
return 0;
}
let out = *x.add(n - 1) >> (64 - s);
for i in (1..n).rev() {
*r.add(i) = (*x.add(i) << s) | (*x.add(i - 1) >> (64 - s));
}
*r = *x << s;
out
}
}
#[inline]
pub(super) unsafe fn rshift(r: *mut u64, x: *const u64, n: usize, s: u32) {
unsafe {
if s == 0 {
if r.cast_const() != x {
ptr::copy_nonoverlapping(x, r, n);
}
return;
}
for i in 0..n - 1 {
*r.add(i) = (*x.add(i) >> s) | (*x.add(i + 1) << (64 - s));
}
*r.add(n - 1) = *x.add(n - 1) >> s;
}
}
pub(super) fn tdiv_itch(nn: usize, dn: usize) -> usize {
if dn <= 2 {
nn + 1
} else {
1 + dn + (nn + 1) + qr_itch(nn + 1, dn).max(qr_itch(nn, dn))
}
}
pub(super) unsafe fn tdiv_qr(
q: *mut u64,
n: *mut u64,
nn: usize,
d: *const u64,
dn: usize,
t: *mut u64,
) {
debug_assert!(nn >= dn && dn >= 1);
unsafe {
let s = (*d.add(dn - 1)).leading_zeros();
match dn {
1 => {
let d0 = *d << s;
let v = invert_limb(d0);
let top = lshift(t, n, nn, s);
*n = div_qr_1::<true>(q, t, nn, top, d0, v) >> s;
}
2 => {
let (d1, d0) = (*d.add(1), *d);
let (d1, d0) = if s == 0 {
(d1, d0)
} else {
((d1 << s) | (d0 >> (64 - s)), d0 << s)
};
let v = invert_3by2(d1, d0);
*t.add(nn) = lshift(t, n, nn, s);
let (r1, r0) = div_qr_2(q, t, nn + 1, d1, d0, v);
*n = if s == 0 {
r0
} else {
(r0 >> s) | (r1 << (64 - s))
};
*n.add(1) = r1 >> s;
}
_ => {
let dd = t.add((t.addr() / 8) & 1);
lshift(dd, d, dn, s);
let u = dd.add(dn);
*u.add(nn) = lshift(u, n, nn, s);
let v = invert_3by2(*dd.add(dn - 1), *dd.add(dn - 2));
let w = u.add(nn + 1);
let wide = *u.add(nn) != 0;
let un = if wide { nn + 1 } else { nn };
let qh = div_qr(q, u, un, dd, dn, v, w);
if wide {
debug_assert!(!qh);
} else {
*q.add(nn - dn) = u64::from(qh);
}
rshift(n, u, dn, s);
}
}
}
}
#[inline(always)]
pub(super) unsafe fn with_scratch<R>(
t: *mut u64,
avail: usize,
need: usize,
f: impl FnOnce(*mut u64) -> R,
) -> R {
if need <= avail {
f(t)
} else {
more_scratch(need, f)
}
}
#[cold]
#[inline(never)]
fn more_scratch<R>(need: usize, f: impl FnOnce(*mut u64) -> R) -> R {
let run = |s: *mut MaybeUninit<u64>| f(s.cast());
if need <= super::super::mul::STACK_LIMBS {
on_stack(run)
} else {
on_heap(need, run)
}
}
pub(super) fn mul_bound(x: usize, y: usize) -> usize {
3 * (x + y) + 64
}
pub(super) fn divexact_itch(nn: usize, dn: usize) -> usize {
(nn + 1) + (dn + 1) + tdiv_itch(nn, dn)
}
const HENSEL_LIMIT: usize = 1 << 18;
pub(super) unsafe fn divexact(
q: *mut u64,
n: *const u64,
nn: usize,
d: *const u64,
dn: usize,
t: *mut u64,
avail: usize,
) {
let qn = nn - dn + 1;
let m = qn.min(dn);
unsafe {
let (nc, dc) = (t, t.add(nn + 1));
if dn >= DC_DIV && qn * m - m * m / 2 >= HENSEL_LIMIT {
ptr::copy_nonoverlapping(n, nc, nn);
let room = avail.saturating_sub(nn + 1 + dn + 1);
with_scratch(dc, room, tdiv_itch(nn, dn), |w| {
tdiv_qr(q, nc, nn, d, dn, w)
});
return;
}
let mut skip = 0;
while *d.add(skip) == 0 {
skip += 1;
}
let bits = (*d.add(skip)).trailing_zeros();
let take = |p: *const u64, len: usize, out: *mut u64, want: usize| {
let have = len - skip;
let k = have.min(want + 1);
ptr::copy_nonoverlapping(p.add(skip), out, k);
if k < want + 1 {
ptr::write_bytes(out.add(k), 0, want + 1 - k);
}
rshift(out, out, want + 1, bits);
};
take(n, nn, nc, qn);
let dl = (dn - skip).min(qn);
take(d, dn, dc, dl);
bdiv_q(q, nc, qn, dc, dl, binvert_limb(*dc));
}
}
pub(super) enum Step {
Size(usize),
Gcd(*const u64, usize),
}
pub(super) unsafe fn subdiv(
a: &mut *mut u64,
b: &mut *mut u64,
n: usize,
t: *mut u64,
avail: usize,
) -> Step {
unsafe {
let mut an = normalized(*a, n);
let mut bn = normalized(*b, n);
let swap = match an.cmp(&bn) {
Ordering::Equal => match cmp_n(*a, *b, an) {
Ordering::Equal => return Step::Gcd(*a, an),
order => order == Ordering::Greater,
},
order => order == Ordering::Greater,
};
if swap {
core::mem::swap(a, b);
core::mem::swap(&mut an, &mut bn);
}
if an == 0 {
return Step::Gcd(*b, bn);
}
let (a, b) = (*a, *b);
let need = (bn - an + 1) + tdiv_itch(bn, an);
with_scratch(t, avail, need, |t| {
tdiv_qr(t, b, bn, a, an, t.add(bn - an + 1))
});
if normalized(b, an) == 0 {
return Step::Gcd(a, an);
}
Step::Size(an)
}
}
#[inline(always)]
pub(super) unsafe fn top_limbs(a: *const u64, b: *const u64, n: usize) -> (u64, u64, u64, u64) {
unsafe {
let (a1, a0, b1, b0) = (*a.add(n - 1), *a.add(n - 2), *b.add(n - 1), *b.add(n - 2));
let s = (a1 | b1).leading_zeros();
if s == 0 {
return (a1, a0, b1, b0);
}
let (a_, b_) = if n > 2 {
(*a.add(n - 3), *b.add(n - 3))
} else {
(0, 0)
};
(
(a1 << s) | (a0 >> (64 - s)),
(a0 << s) | (a_ >> (64 - s)),
(b1 << s) | (b0 >> (64 - s)),
(b0 << s) | (b_ >> (64 - s)),
)
}
}
pub(super) unsafe fn gcd_lehmer(
g: *mut u64,
mut a: *mut u64,
mut b: *mut u64,
mut n: usize,
t: *mut u64,
avail: usize,
) -> usize {
unsafe {
let mut ones = false;
while n > 2 {
let (ah, al, bh, bl) = top_limbs(a, b, n);
if let Some(m) = hgcd2_hinted(ah, al, bh, bl, &mut ones) {
reduce_pair(a, b, n, m);
n -= usize::from(*a.add(n - 1) | *b.add(n - 1) == 0);
} else {
match subdiv(&mut a, &mut b, n, t, avail) {
Step::Size(k) => n = k,
Step::Gcd(p, k) => {
ptr::copy(p, g, k);
return k;
}
}
}
}
let x = if n == 2 {
join(*a.add(1), *a)
} else {
u128::from(*a)
};
let y = if n == 2 {
join(*b.add(1), *b)
} else {
u128::from(*b)
};
let r = gcd_u128(x, y);
*g = r as u64;
if high(r) != 0 {
*g.add(1) = high(r);
2
} else {
1
}
}
}
pub(super) unsafe fn rem_1(a: *const u64, n: usize, d: u64, t: *mut u64) -> u64 {
unsafe {
if n == 1 {
return *a % d;
}
let s = d.leading_zeros();
let d0 = d << s;
let v = invert_limb(d0);
let top = lshift(t, a, n, s);
div_qr_1::<false>(t, t, n, top, d0, v) >> s
}
}
pub(super) struct Row {
pub(super) u0: *mut u64,
pub(super) u1: *mut u64,
pub(super) un: usize,
}
#[derive(Clone, Copy)]
pub(super) struct Cofactor {
pub(super) p: *const u64,
pub(super) n: usize,
pub(super) negative: bool,
}
impl Row {
#[inline(always)]
unsafe fn of_a(&self) -> Cofactor {
let n = unsafe { normalized(self.u1, self.un) };
Cofactor {
p: self.u1,
n,
negative: false,
}
}
#[inline(always)]
unsafe fn of_b(&self) -> Cofactor {
let n = unsafe { normalized(self.u0, self.un) };
Cofactor {
p: self.u0,
n,
negative: n != 0,
}
}
#[inline(always)]
pub(super) unsafe fn smaller(&self) -> Cofactor {
unsafe {
if cmp_n(self.u0, self.u1, self.un) == Ordering::Less {
self.of_b()
} else {
self.of_a()
}
}
}
unsafe fn add_times(&mut self, to_u0: bool, q: *const u64, qn: usize, t: *mut u64) {
let (to, from) = if to_u0 {
(self.u0, self.u1.cast_const())
} else {
(self.u1, self.u0.cast_const())
};
unsafe {
let un = self.un;
if qn == 1 {
let c = if *q == 1 {
u64::from(add_n(to, to, from, un))
} else {
addmul_1(to, from, un, *q)
};
*to.add(un) = c;
self.un = un + usize::from(c != 0);
return;
}
let fnorm = normalized(from, un);
if fnorm == 0 {
return;
}
let (long, short) = if qn >= fnorm {
((q, qn), (from, fnorm))
} else {
((from, fnorm), (q, qn))
};
let mut pn = fnorm + qn;
mul(t, long.0, long.1, short.0, short.1, t.add(pn));
pn -= usize::from(*t.add(pn - 1) == 0);
if pn >= un {
let c = add_to(t, t, pn, to, un);
ptr::copy_nonoverlapping(t, to, pn);
*to.add(pn) = c;
self.un = pn + usize::from(c != 0);
} else {
let c = add_to(to, to, un, t, pn);
*to.add(un) = c;
self.un = un + usize::from(c != 0);
}
}
}
}
pub(super) fn add_times_itch(un: usize, qn: usize) -> usize {
if qn == 1 {
0
} else {
un + qn + mul_bound(un, qn)
}
}
pub(super) unsafe fn add_to(
r: *mut u64,
a: *const u64,
na: usize,
b: *const u64,
nb: usize,
) -> u64 {
unsafe {
let c = add_n(r, a, b, nb);
if r.cast_const() != a {
ptr::copy_nonoverlapping(a.add(nb), r.add(nb), na - nb);
}
add_1(r.add(nb), na - nb, u64::from(c))
}
}
pub(super) unsafe fn sub_from(
r: *mut u64,
a: *const u64,
na: usize,
b: *const u64,
nb: usize,
) -> u64 {
unsafe {
let c = sub_n(r, a, b, nb);
if r.cast_const() != a {
ptr::copy_nonoverlapping(a.add(nb), r.add(nb), na - nb);
}
sub_1(r.add(nb), na - nb, u64::from(c))
}
}
pub(super) struct Found {
pub(super) g: *const u64,
pub(super) gn: usize,
pub(super) cofactor: Cofactor,
}
pub(super) enum StepExt {
Size(usize),
Gcd(Found),
}
pub(super) unsafe fn subdiv_ext(
a: *mut u64,
b: *mut u64,
n: usize,
row: &mut Row,
t: *mut u64,
avail: usize,
) -> StepExt {
unsafe {
let (mut pa, mut pb) = (a, b);
let mut an = normalized(pa, n);
let mut bn = normalized(pb, n);
debug_assert!(an > 0 && bn > 0);
let swap = match an.cmp(&bn) {
Ordering::Equal => match cmp_n(pa, pb, an) {
Ordering::Equal => {
return StepExt::Gcd(Found {
g: pa,
gn: an,
cofactor: row.smaller(),
});
}
order => order == Ordering::Greater,
},
order => order == Ordering::Greater,
};
if swap {
core::mem::swap(&mut pa, &mut pb);
core::mem::swap(&mut an, &mut bn);
core::mem::swap(&mut row.u0, &mut row.u1);
}
let borrow = sub_from(pb, pb, bn, pa, an);
debug_assert!(borrow == 0);
bn = normalized(pb, bn);
let order = if bn == an {
cmp_n(pb, pa, an)
} else {
bn.cmp(&an)
};
let step = if order == Ordering::Equal {
StepExt::Gcd(Found {
g: pa,
gn: an,
cofactor: of_a(row, swap),
})
} else {
row.add_times(true, &1, 1, t);
if order == Ordering::Greater {
let qn = bn - an + 1;
let (q, w) = (t, t.add(qn));
let room = avail.saturating_sub(qn);
with_scratch(w, room, tdiv_itch(bn, an), |w| {
tdiv_qr(q, pb, bn, pa, an, w)
});
if normalized(pb, an) == 0 {
StepExt::Gcd(Found {
g: pa,
gn: an,
cofactor: of_a(row, swap),
})
} else {
let qn = normalized(q, qn);
if qn > 0 {
with_scratch(w, room, add_times_itch(row.un, qn), |w| {
row.add_times(true, q, qn, w)
});
}
StepExt::Size(an)
}
} else {
StepExt::Size(an)
}
};
if swap {
core::mem::swap(&mut row.u0, &mut row.u1);
}
step
}
}
#[inline(always)]
unsafe fn of_a(row: &Row, swapped: bool) -> Cofactor {
let n = unsafe { normalized(row.u1, row.un) };
Cofactor {
p: row.u1,
n,
negative: swapped && n != 0,
}
}
pub(super) fn lehmer_ext_itch(n: usize) -> usize {
(n + 1) + tdiv_itch(n, 1).max(2 * n + 4 + mul_bound(n + 1, n + 1))
}
#[allow(clippy::too_many_arguments)]
pub(super) unsafe fn gcdext_lehmer(
g: *mut u64,
c: *mut u64,
a: *mut u64,
b: *mut u64,
mut n: usize,
row: &mut Row,
t: *mut u64,
avail: usize,
) -> (usize, usize, bool) {
unsafe {
let mut ones = false;
while n >= 2 {
let (ah, al, bh, bl) = top_limbs(a, b, n);
if let Some(m) = hgcd2_hinted(ah, al, bh, bl, &mut ones) {
reduce_pair(a, b, n, m);
n -= usize::from(*a.add(n - 1) | *b.add(n - 1) == 0);
let un = row.un;
let (c0, c1) = extend_row(row.u0, row.u1, un, m);
*row.u0.add(un) = c0;
*row.u1.add(un) = c1;
row.un = un + usize::from(c0 | c1 != 0);
} else {
match subdiv_ext(a, b, n, row, t, avail) {
StepExt::Size(k) => n = k,
StepExt::Gcd(found) => {
ptr::copy(found.g, g, found.gn);
let f = found.cofactor;
ptr::copy(f.p, c, f.n);
return (found.gn, f.n, f.negative);
}
}
}
}
let (x, y) = (*a, *b);
if x == y {
*g = x;
let f = row.smaller();
ptr::copy(f.p, c, f.n);
return (1, f.n, f.negative);
}
let (gl, u, v) = gcdext_1(x, y);
*g = gl;
let un = row.un;
if u == 0 {
let f = row.of_b();
ptr::copy(f.p, c, f.n);
return (1, f.n, f.negative);
}
if v == 0 {
let f = row.of_a();
ptr::copy(f.p, c, f.n);
return (1, f.n, f.negative);
}
let negative = u < 0;
let (u, v) = (u.unsigned_abs(), v.unsigned_abs());
ptr::write_bytes(c, 0, un + 2);
*c.add(un) = addmul_1(c, row.u1, un, u);
let carry = addmul_1(c, row.u0, un, v);
let (top, over) = (*c.add(un)).overflowing_add(carry);
*c.add(un) = top;
*c.add(un + 1) = u64::from(over);
(1, normalized(c, un + 2), negative)
}
}