use super::super::mul::limbs::mul;
use super::super::root::{mul_1, sqrt_rem_u128};
use super::Limbs;
use super::primes::{Remainders, Sieve};
use super::product::{Packer, product, squared, times};
use core::mem::MaybeUninit;
pub(super) const SWING: u64 = 256;
pub(super) const SMALL_LIMBS: usize = 32;
pub(super) const ODD_FACTORIALS: [u64; 26] = {
let mut t = [1u64; 26];
let mut i = 1;
while i < 26 {
let odd = (i as u64) >> (i as u64).trailing_zeros();
t[i] = t[i - 1] * odd;
i += 1;
}
t
};
const ODD_DOUBLE_FACTORIALS: [u64; 17] = {
let mut t = [1u64; 17];
let mut i = 1;
while i < 17 {
t[i] = t[i - 1] * (2 * i as u64 + 1);
i += 1;
}
t
};
pub(super) fn odd_factorial(n: u64) -> Limbs {
if n < ODD_FACTORIALS.len() as u64 {
return vec![ODD_FACTORIALS[n as usize]];
}
let mut levels = 0;
while n >> levels >= SWING {
levels += 1;
}
let mut small = [0u64; SMALL_LIMBS];
let len = small_odd_factorial(n >> levels, &mut small);
let mut x = Vec::with_capacity(len + 1);
x.extend_from_slice(&small[..len]);
if levels > 0 {
let sieve = Sieve::new(n);
for level in (0..levels).rev() {
let m = n >> level;
x = times(&squared(&x), &swing(m, &sieve));
}
}
x
}
pub(super) fn small_odd_factorial(m: u64, out: &mut [u64; SMALL_LIMBS]) -> usize {
debug_assert!(m < SWING);
if m < ODD_FACTORIALS.len() as u64 {
out[0] = ODD_FACTORIALS[m as usize];
return 1;
}
let mut f = [MaybeUninit::<u64>::uninit(); 64];
let mut nf = 0;
let limit = u64::MAX / (m * m);
let mut prod = 1u64;
let mut top = m;
while top >= 3 {
if top <= 33 {
f[nf] = MaybeUninit::new(ODD_DOUBLE_FACTORIALS[(top as usize - 1) / 2]);
nf += 1;
} else {
f[nf] = MaybeUninit::new(ODD_DOUBLE_FACTORIALS[16]);
nf += 1;
let mut j = 35;
while j <= top {
let pair = if j + 2 <= top { j * (j + 2) } else { j };
if prod > limit {
f[nf] = MaybeUninit::new(prod);
nf += 1;
prod = pair;
} else {
prod *= pair;
}
j += 4;
}
}
top /= 2;
}
f[nf] = MaybeUninit::new(prod);
nf += 1;
small_product(
unsafe { core::slice::from_raw_parts(f.as_ptr().cast(), nf) },
out,
)
}
pub(super) fn small_product(f: &[u64], out: &mut [u64; SMALL_LIMBS]) -> usize {
let mut a = [MaybeUninit::<u64>::uninit(); SMALL_LIMBS];
let mut b = [MaybeUninit::<u64>::uninit(); SMALL_LIMBS];
let (a, b) = (a.as_mut_ptr().cast::<u64>(), b.as_mut_ptr().cast::<u64>());
let (mut na, mut nb) = (1, 1);
unsafe {
*a = f[0];
*b = 1;
let mut i = 1;
while i < f.len() {
let carry = mul_1(b, b, nb, f[i]);
if carry != 0 {
*b.add(nb) = carry;
nb += 1;
}
if i + 1 < f.len() {
let carry = mul_1(a, a, na, f[i + 1]);
if carry != 0 {
*a.add(na) = carry;
na += 1;
}
}
i += 2;
}
let ((x, nx), (y, ny)) = if na >= nb {
((a, na), (b, nb))
} else {
((b, nb), (a, na))
};
mul(out.as_mut_ptr(), x, nx, y, ny, core::ptr::null_mut());
let mut n = nx + ny;
while out[n - 1] == 0 {
n -= 1;
}
n
}
}
fn swing(m: u64, sieve: &Sieve) -> Limbs {
let mut packer = Packer::new(m, (m / 64) as usize + 4);
let root = sqrt_rem_u128(u128::from(m)).0;
sieve.each(3, root, |p| packer.push(prime_power(p, m)));
let mut r = Remainders::of(m);
sieve.each(root + 1, m / 3, |p| {
if (m ^ r.of_divisor(p)) & 1 == 1 {
packer.push(p);
}
});
sieve.each(m / 2 + 1, m, |p| packer.push(p));
product(&packer.finish())
}
fn prime_power(p: u64, m: u64) -> u64 {
let mut q = m / p;
let mut f = 1;
while q > 0 {
if q & 1 == 1 {
f *= p;
}
q /= p;
}
f
}