#[inline]
pub(super) fn mul_low<const K: usize>(a: &[u64; K], b: &[u64; K]) -> [u64; K] {
#[cfg(target_arch = "wasm32")]
{
mul_low_u32(a, b)
}
#[cfg(not(target_arch = "wasm32"))]
{
mul_low_u64(a, b)
}
}
#[inline]
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub(super) fn mul_low_u64<const K: usize>(a: &[u64; K], b: &[u64; K]) -> [u64; K] {
let mut out = [0u64; K];
for i in 0..K {
let mut carry: u128 = 0;
let mut j = 0;
while i + j < K {
let idx = i + j;
let t = (a[i] as u128) * (b[j] as u128) + (out[idx] as u128) + carry;
out[idx] = t as u64;
carry = t >> 64;
j += 1;
}
}
out
}
#[inline]
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
pub(super) fn mul_low_u32<const K: usize>(a: &[u64; K], b: &[u64; K]) -> [u64; K] {
debug_assert!(K <= 32, "FixedInt u32-digit scratch supports only K <= 32");
let n = 2 * K;
let mut ad = [0u32; 64];
let mut bd = [0u32; 64];
for i in 0..K {
ad[2 * i] = a[i] as u32;
ad[2 * i + 1] = (a[i] >> 32) as u32;
bd[2 * i] = b[i] as u32;
bd[2 * i + 1] = (b[i] >> 32) as u32;
}
let mut out = [0u32; 64];
for i in 0..n {
let d = ad[i] as u64;
if d == 0 {
continue;
}
let mut carry: u64 = 0;
let mut j = 0;
while i + j < n {
let idx = i + j;
let t = d * (bd[j] as u64) + (out[idx] as u64) + carry;
out[idx] = t as u32;
carry = t >> 32;
j += 1;
}
}
let mut res = [0u64; K];
for i in 0..K {
res[i] = (out[2 * i] as u64) | ((out[2 * i + 1] as u64) << 32);
}
res
}
#[inline]
pub(super) fn mul_full<const K: usize>(ma: &[u64; K], mb: &[u64; K], full: &mut [u64; 64]) {
debug_assert!(
full[..2 * K].iter().all(|&limb| limb == 0),
"mul_full requires a zeroed output buffer (digit paths diverge on dirty input)"
);
#[cfg(target_arch = "wasm32")]
{
mul_full_u32(ma, mb, full)
}
#[cfg(not(target_arch = "wasm32"))]
{
mul_full_u64(ma, mb, full)
}
}
#[inline]
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub(super) fn mul_full_u64<const K: usize>(ma: &[u64; K], mb: &[u64; K], full: &mut [u64; 64]) {
for i in 0..K {
let mut carry: u128 = 0;
for j in 0..K {
let idx = i + j;
let t = (ma[i] as u128) * (mb[j] as u128) + (full[idx] as u128) + carry;
full[idx] = t as u64;
carry = t >> 64;
}
full[i + K] = carry as u64;
}
}
#[inline]
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
pub(super) fn mul_full_u32<const K: usize>(ma: &[u64; K], mb: &[u64; K], full: &mut [u64; 64]) {
debug_assert!(K <= 32, "FixedInt u32-digit scratch supports only K <= 32");
let n = 2 * K;
let mut ad = [0u32; 64];
let mut bd = [0u32; 64];
for i in 0..K {
ad[2 * i] = ma[i] as u32;
ad[2 * i + 1] = (ma[i] >> 32) as u32;
bd[2 * i] = mb[i] as u32;
bd[2 * i + 1] = (mb[i] >> 32) as u32;
}
let mut out = [0u32; 128];
for i in 0..n {
let d = ad[i] as u64;
if d == 0 {
continue;
}
let mut carry: u64 = 0;
for j in 0..n {
let idx = i + j;
let t = d * (bd[j] as u64) + (out[idx] as u64) + carry;
out[idx] = t as u32;
carry = t >> 32;
}
out[i + n] = carry as u32;
}
for (i, slot) in full.iter_mut().enumerate().take(n) {
*slot = (out[2 * i] as u64) | ((out[2 * i + 1] as u64) << 32);
}
}