use crate::arch::ntt::K;
use num_modular::ModularCoreOps;
pub trait CrtAccum: Default + Copy {
type Lane: Copy
+ Default
+ Into<u128>
+ for<'a> ModularCoreOps<Self::Lane, &'a Self::Lane, Output = Self::Lane>;
fn from_lane(v: Self::Lane) -> Self;
fn add_product(&mut self, t: Self::Lane, factor: u128);
fn rem_lane(&self, m: Self::Lane) -> Self::Lane;
fn write_words(&self, out: &mut [crate::arch::word::Word; 6]) -> u32;
}
pub fn garner_combine<A: CrtAccum>(
residues: &[A::Lane],
crt_inv_ij: &[[A::Lane; K]; K],
primes: &[A::Lane; K],
) -> A {
let k = residues.len();
assert!(k <= K, "CRT supports up to {K} primes");
let p0 = primes[0];
let p1 = primes[1];
let p2 = primes[2];
let mut x = A::from_lane(residues[0]);
if k == 1 {
return x;
}
let x_mod_p1 = x.rem_lane(p1);
let diff1 = residues[1].subm(x_mod_p1, &p1);
let t1 = diff1.mulm(crt_inv_ij[0][1], &p1);
x.add_product(t1, p0.into());
if k == 2 {
return x;
}
let x_mod_p2 = x.rem_lane(p2);
let diff2 = residues[2].subm(x_mod_p2, &p2);
let inv_prod = crt_inv_ij[0][2].mulm(crt_inv_ij[1][2], &p2);
let t2 = diff2.mulm(inv_prod, &p2);
x.add_product(t2, p0.into() * p1.into());
x
}
#[cfg(not(any(force_bits = "32", target_pointer_width = "32")))]
mod triple_impl {
use super::CrtAccum;
use crate::arch::word::TripleWord;
impl CrtAccum for TripleWord {
type Lane = u64;
#[inline]
fn from_lane(v: u64) -> Self {
TripleWord([v, 0, 0])
}
#[inline]
fn add_product(&mut self, t: u64, factor: u128) {
let fac_lo = factor as u64;
let fac_hi = (factor >> 64) as u64;
let m_lo_full = (t as u128) * (fac_lo as u128);
let lo = m_lo_full as u64;
let m_lo = (m_lo_full >> 64) as u64;
let m_hi_full = (t as u128) * (fac_hi as u128);
let m_hi = m_hi_full as u64;
let hi = (m_hi_full >> 64) as u64;
let (mid, c) = m_lo.overflowing_add(m_hi);
let hi_word = hi.wrapping_add(c as u64);
let (r0, c0) = self.0[0].overflowing_add(lo);
self.0[0] = r0;
let (r1, c1) = self.0[1].overflowing_add(mid.wrapping_add(c0 as u64));
self.0[1] = r1;
self.0[2] = self.0[2].wrapping_add(hi_word.wrapping_add(c1 as u64));
}
#[inline]
fn rem_lane(&self, m: u64) -> u64 {
let m128 = m as u128;
let mut r: u128 = 0;
for &word in self.0.iter().rev() {
r = (r << 64) | (word as u128);
r %= m128;
}
r as u64
}
#[inline]
fn write_words(&self, out: &mut [crate::arch::word::Word; 6]) -> u32 {
out[0] = self.0[0];
out[1] = self.0[1];
out[2] = self.0[2];
if self.0[2] != 0 {
3
} else if self.0[1] != 0 {
2
} else {
1
}
}
}
}
#[cfg(any(force_bits = "32", target_pointer_width = "32"))]
mod triple_impl {
use super::CrtAccum;
use crate::arch::word::TripleWord;
impl CrtAccum for TripleWord {
type Lane = u32;
#[inline]
fn from_lane(v: u32) -> Self {
TripleWord([v, 0, 0])
}
#[inline]
fn add_product(&mut self, t: u32, factor: u128) {
let factor_lo = factor as u32;
let factor_hi = (factor >> 32) as u32;
let m_lo = (t as u64) * (factor_lo as u64);
let lo = m_lo as u32;
let m_mid = (m_lo >> 32) as u32;
let m_hi = (t as u64) * (factor_hi as u64) + m_mid as u64;
let mid = m_hi as u32;
let hi = (m_hi >> 32) as u32;
let (r0, c0) = self.0[0].overflowing_add(lo);
self.0[0] = r0;
let (r1, c1) = self.0[1].overflowing_add(mid.wrapping_add(c0 as u32));
self.0[1] = r1;
self.0[2] = self.0[2].wrapping_add(hi.wrapping_add(c1 as u32));
}
#[inline]
fn rem_lane(&self, m: u32) -> u32 {
let m64 = m as u64;
let mut r: u64 = 0;
for &word in self.0.iter().rev() {
r = (r << 32) | (word as u64);
r %= m64;
}
r as u32
}
#[inline]
fn write_words(&self, out: &mut [crate::arch::word::Word; 6]) -> u32 {
out[0] = self.0[0];
out[1] = self.0[1];
out[2] = self.0[2];
if self.0[2] != 0 {
3
} else if self.0[1] != 0 {
2
} else {
1
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::arch::word::TripleWord;
#[cfg(not(feature = "std"))]
use alloc::vec;
#[test]
fn test_garner_roundtrip() {
use crate::arch::ntt::{CRT_INV_IJ, MODULI};
type Lane = <TripleWord as CrtAccum>::Lane;
let p0 = MODULI[0];
let p1 = MODULI[1];
let p2 = MODULI[2];
let primes = [p0, p1, p2];
let residues = vec![12345u64 as Lane, 67890u64 as Lane, 11111u64 as Lane];
let x = garner_combine::<TripleWord>(&residues, &CRT_INV_IJ, &primes);
assert_eq!(x.rem_lane(p0), residues[0]);
assert_eq!(x.rem_lane(p1), residues[1]);
assert_eq!(x.rem_lane(p2), residues[2]);
let x = garner_combine::<TripleWord>(&residues[..2], &CRT_INV_IJ, &primes);
assert_eq!(x.rem_lane(p0), residues[0]);
assert_eq!(x.rem_lane(p1), residues[1]);
let x = garner_combine::<TripleWord>(&residues[..1], &CRT_INV_IJ, &primes);
let mut buf = [crate::arch::word::Word::default(); 6];
x.write_words(&mut buf);
assert_eq!(buf[0], residues[0]);
}
}