use super::{
field_reduction_constants::FieldReductionConstants, limbs::reduce_8_mod_4,
montgomery::MontgomeryLimbs,
};
use ff::PrimeField;
const SMALL_VALUE_MAX: u64 = (1u64 << 62) - 1;
#[inline(never)]
pub(crate) fn to_small_vec_or_zero<F: PrimeField + FieldReductionConstants>(
poly: &[F],
) -> (Vec<i64>, Vec<usize>) {
let mut result = Vec::with_capacity(poly.len());
let mut large_positions = Vec::new();
let p = &<F as FieldReductionConstants>::MODULUS;
for (idx, f) in poly.iter().enumerate() {
let repr = f.to_repr();
let bytes = repr.as_ref();
let l0 = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
let l1 = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
let l2 = u64::from_le_bytes(bytes[16..24].try_into().unwrap());
let l3 = u64::from_le_bytes(bytes[24..32].try_into().unwrap());
if l1 == 0 && l2 == 0 && l3 == 0 && l0 <= SMALL_VALUE_MAX {
result.push(l0 as i64);
continue;
}
let (d0, borrow0) = p[0].overflowing_sub(l0);
let (d1a, borrow1a) = p[1].overflowing_sub(l1);
let (d1, borrow1b) = d1a.overflowing_sub(borrow0 as u64);
let borrow1 = borrow1a | borrow1b;
let (d2a, borrow2a) = p[2].overflowing_sub(l2);
let (d2, borrow2b) = d2a.overflowing_sub(borrow1 as u64);
let borrow2 = borrow2a | borrow2b;
let (d3a, _borrow3a) = p[3].overflowing_sub(l3);
let (d3, _) = d3a.overflowing_sub(borrow2 as u64);
if d1 == 0 && d2 == 0 && d3 == 0 && d0 > 0 && d0 <= SMALL_VALUE_MAX {
result.push(-(d0 as i64));
continue;
}
result.push(0);
large_positions.push(idx);
}
(result, large_positions)
}
#[derive(Clone, Copy)]
pub(crate) struct SmallAccumulator {
pos: [u64; 7],
neg: [u64; 7],
}
impl SmallAccumulator {
#[inline(always)]
pub fn zero() -> Self {
Self {
pos: [0u64; 7],
neg: [0u64; 7],
}
}
#[inline(always)]
pub fn accumulate(&mut self, field_limbs: &[u64; 4], val: i128) {
if val == 0 {
return;
}
let abs_val = val.unsigned_abs();
let target = if val > 0 {
&mut self.pos
} else {
&mut self.neg
};
let lo = abs_val as u64;
let hi = (abs_val >> 64) as u64;
let mut carry = 0u128;
for j in 0..4 {
let prod = (field_limbs[j] as u128) * (lo as u128) + (target[j] as u128) + carry;
target[j] = prod as u64;
carry = prod >> 64;
}
for item in target.iter_mut().take(7).skip(4) {
let sum = (*item as u128) + carry;
*item = sum as u64;
carry = sum >> 64;
if carry == 0 {
break;
}
}
debug_assert!(carry == 0, "SmallAccumulator lo-part carry overflow");
if hi != 0 {
carry = 0;
for j in 0..4 {
let prod = (field_limbs[j] as u128) * (hi as u128) + (target[j + 1] as u128) + carry;
target[j + 1] = prod as u64;
carry = prod >> 64;
}
for item in target.iter_mut().take(7).skip(5) {
let sum = (*item as u128) + carry;
*item = sum as u64;
carry = sum >> 64;
if carry == 0 {
break;
}
}
debug_assert!(carry == 0, "SmallAccumulator hi-part carry overflow");
}
}
#[inline]
pub fn reduce<F: MontgomeryLimbs + FieldReductionConstants + PrimeField>(&self) -> F {
let pos_f = reduce_7_to_field::<F>(&self.pos);
let neg_f = reduce_7_to_field::<F>(&self.neg);
pos_f - neg_f
}
}
impl std::ops::AddAssign for SmallAccumulator {
#[inline]
fn add_assign(&mut self, other: Self) {
let mut carry = 0u128;
for i in 0..7 {
let sum = (self.pos[i] as u128) + (other.pos[i] as u128) + carry;
self.pos[i] = sum as u64;
carry = sum >> 64;
}
debug_assert!(carry == 0, "SmallAccumulator pos AddAssign overflow");
carry = 0;
for i in 0..7 {
let sum = (self.neg[i] as u128) + (other.neg[i] as u128) + carry;
self.neg[i] = sum as u64;
carry = sum >> 64;
}
debug_assert!(carry == 0, "SmallAccumulator neg AddAssign overflow");
}
}
#[inline]
fn reduce_7_to_field<F: MontgomeryLimbs + FieldReductionConstants>(acc: &[u64; 7]) -> F {
if acc[4] == 0 && acc[5] == 0 && acc[6] == 0 {
let mut base = [acc[0], acc[1], acc[2], acc[3]];
for _ in 0..F::MAX_REDC_SUB_CORRECTIONS {
if super::limbs::gte::<4>(&base, &F::MODULUS) {
base = super::limbs::sub::<4>(&base, &F::MODULUS);
}
}
return F::from_limbs(base);
}
let padded = [acc[0], acc[1], acc[2], acc[3], acc[4], acc[5], acc[6], 0];
let reduced = reduce_8_mod_4(&padded, &F::MODULUS);
F::from_limbs(reduced)
}
#[cfg(test)]
#[macro_export]
macro_rules! test_small_value {
($mod_name:ident, $field:ty) => {
mod $mod_name {
#[test]
fn small_vec_or_zero() {
$crate::big_num::small_value::tests::test_small_vec_or_zero_impl::<$field>();
}
#[test]
fn small_accumulator() {
$crate::big_num::small_value::tests::test_small_accumulator_impl::<$field>();
}
#[test]
fn small_accumulator_i128() {
$crate::big_num::small_value::tests::test_small_accumulator_i128_impl::<$field>();
}
#[test]
fn small_accumulator_few_products() {
$crate::big_num::small_value::tests::test_small_accumulator_few_products_impl::<$field>();
}
}
};
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
pub(crate) fn test_small_vec_or_zero_impl<
F: PrimeField + FieldReductionConstants + MontgomeryLimbs + Copy,
>() {
use rand::{SeedableRng, rngs::StdRng};
let mut rng = StdRng::seed_from_u64(11111);
let vals: Vec<F> = (0..10).map(|i| F::from(i as u64)).collect();
let (small, large) = to_small_vec_or_zero(&vals);
assert!(large.is_empty());
for (i, &v) in small.iter().enumerate() {
assert_eq!(v, i as i64);
}
let mixed = vec![
F::from(5u64),
F::random(&mut rng), -F::from(3u64),
F::random(&mut rng), F::from(100u64),
];
let (small, large) = to_small_vec_or_zero(&mixed);
assert_eq!(small.len(), 5);
assert_eq!(small[0], 5);
assert_eq!(small[1], 0); assert_eq!(small[2], -3);
assert_eq!(small[3], 0); assert_eq!(small[4], 100);
assert_eq!(large, vec![1, 3]);
let boundary = vec![F::from(SMALL_VALUE_MAX), -F::from(SMALL_VALUE_MAX)];
let (small, large) = to_small_vec_or_zero(&boundary);
assert!(large.is_empty(), "values at threshold should be small");
assert_eq!(small[0], SMALL_VALUE_MAX as i64);
assert_eq!(small[1], -(SMALL_VALUE_MAX as i64));
let above = vec![F::from(SMALL_VALUE_MAX + 1)];
let (small, large) = to_small_vec_or_zero(&above);
assert_eq!(large, vec![0], "values above threshold should be large");
assert_eq!(small[0], 0);
}
pub(crate) fn test_small_accumulator_impl<
F: PrimeField + FieldReductionConstants + MontgomeryLimbs + Copy,
>() {
use rand::{SeedableRng, rngs::StdRng};
let mut rng = StdRng::seed_from_u64(12345);
let n = 1000;
let eq_vals: Vec<F> = (0..n).map(|_| F::random(&mut rng)).collect();
let small_vals: Vec<i64> = (0..n)
.map(|i| ((i as i64) % 201) - 100) .collect();
let expected: F = eq_vals
.iter()
.zip(small_vals.iter())
.map(|(&e, &s)| {
if s >= 0 {
e * F::from(s as u64)
} else {
-(e * F::from((-s) as u64))
}
})
.sum();
let mut acc = SmallAccumulator::zero();
for (&e, &s) in eq_vals.iter().zip(small_vals.iter()) {
acc.accumulate(e.to_limbs(), s as i128);
}
let result = acc.reduce::<F>();
assert_eq!(result, expected, "SmallAccumulator sum mismatch");
}
pub(crate) fn test_small_accumulator_i128_impl<
F: PrimeField + FieldReductionConstants + MontgomeryLimbs + Copy,
>() {
use rand::{SeedableRng, rngs::StdRng};
let mut rng = StdRng::seed_from_u64(54321);
let n = 100;
let eq_vals: Vec<F> = (0..n).map(|_| F::random(&mut rng)).collect();
let a_vals: Vec<i64> = (0..n).map(|i| (i as i64 + 1) * 1_000_000).collect();
let b_vals: Vec<i64> = (0..n).map(|i| (i as i64 + 1) * 2_000_000).collect();
let expected: F = eq_vals
.iter()
.zip(a_vals.iter().zip(b_vals.iter()))
.map(|(&e, (&a, &b))| {
let prod = a as i128 * b as i128;
if prod >= 0 {
e * F::from(prod as u64)
} else {
-(e * F::from((-prod) as u64))
}
})
.sum();
let mut acc = SmallAccumulator::zero();
for (&e, (&a, &b)) in eq_vals.iter().zip(a_vals.iter().zip(b_vals.iter())) {
let prod = a as i128 * b as i128;
acc.accumulate(e.to_limbs(), prod);
}
let result = acc.reduce::<F>();
assert_eq!(result, expected, "SmallAccumulator i128 sum mismatch");
}
pub(crate) fn test_small_accumulator_few_products_impl<
F: PrimeField + FieldReductionConstants + MontgomeryLimbs + Copy,
>() {
use rand::{SeedableRng, rngs::StdRng};
let mut rng = StdRng::seed_from_u64(77777);
let max_n = F::MAX_REDC_SUB_CORRECTIONS + 2;
for n in 1..=max_n {
let eq_vals: Vec<F> = (0..n).map(|_| F::random(&mut rng)).collect();
let expected: F = eq_vals.iter().copied().sum();
let mut acc = SmallAccumulator::zero();
for &e in &eq_vals {
acc.accumulate(e.to_limbs(), 1i128);
}
let result = acc.reduce::<F>();
assert_eq!(
result, expected,
"SmallAccumulator few-products mismatch (n={n})"
);
}
}
}