use crate::{
arch::{
self,
word::{DoubleWord, Word},
},
math::{mul_add_2carry, mul_add_carry},
primitive::{double_word, split_dword},
};
pub fn square(b: &mut [Word], a: &[Word]) {
debug_assert!(b.len() == a.len() * 2);
let n = a.len();
let mut cy: Word = 0;
let mut i = 0;
while i + 2 < n {
let m0 = a[i];
let m1 = a[i + 1];
let rhs = &a[i + 2..];
let base = 2 * i + 2;
let (lo, hi) = mul_add_carry(m0, m1, 0);
let (v, c1) = b[base - 1].overflowing_add(lo);
b[base - 1] = v;
let init_lo = hi.wrapping_add(Word::from(c1));
let mut carry_lo = init_lo;
let mut carry_hi: Word = 0;
for (x, &y) in b[base..base + rhs.len()].iter_mut().zip(rhs.iter()) {
(*x, carry_lo) = mul_add_2carry(y, m0, *x, carry_lo);
(carry_lo, carry_hi) = mul_add_2carry(y, m1, carry_lo, carry_hi);
}
let p = base + rhs.len(); let (s, c) = arch::add::add_with_carry(b[p], carry_lo, cy);
b[p] = s;
let (s, c2) = arch::add::add_with_carry(b[p + 1], carry_hi, Word::from(c));
b[p + 1] = s;
cy = Word::from(c2);
i += 2;
}
if i == n - 2 {
let (lo, hi) = mul_add_carry(a[i], a[i + 1], 0);
let base = 2 * i + 1; let (v, c1) = b[base].overflowing_add(lo);
b[base] = v;
let (s, c) = arch::add::add_with_carry(b[base + 1], hi, Word::from(c1));
let (s, c2) = arch::add::add_with_carry(s, cy, Word::from(c));
b[base + 1] = s;
cy = Word::from(c2);
}
let (mut c1, mut c2) = (false, false);
for (m, b01) in a.iter().zip(b.chunks_exact_mut(2)) {
let b0 = b01.first().unwrap();
let b1 = b01.last().unwrap();
let (s0, s1) = mul_add_2carry(*m, *m, *b0, *b0);
let s = double_word(s0, s1);
let wb1 = double_word(0, *b1);
let (s, oc1) = s.overflowing_add(wb1 + c1 as DoubleWord);
let (s, oc2) = s.overflowing_add(wb1 + c2 as DoubleWord);
let (s0, s1) = split_dword(s);
*b01.first_mut().unwrap() = s0;
*b01.last_mut().unwrap() = s1;
c1 = oc1;
c2 = oc2;
}
*b.last_mut().unwrap() += cy + c1 as Word + c2 as Word;
}
#[cfg(test)]
mod tests {
use crate::arch::word::Word;
use crate::UBig;
use alloc::vec;
use alloc::vec::Vec;
fn lcg_words(seed: u64, words: usize) -> UBig {
let mut limbs: Vec<Word> = Vec::with_capacity(words);
let mut s = seed.wrapping_add(words as u64);
for _ in 0..words {
s = s
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
limbs.push(s as Word);
}
if let Some(top) = limbs.last_mut() {
*top |= 1; }
UBig::from_words(&limbs)
}
#[test]
fn sqr_matches_mul_simple_range() {
for words in 2..=128 {
for seed in 0..6u64 {
let a = lcg_words(seed, words);
assert_eq!(a.sqr(), &a * &a, "sqr != mul at words={words} seed={seed}");
}
}
}
#[test]
fn sqr_matches_mul_adversarial() {
for words in [2, 3, 4, 5, 7, 8, 15, 16, 17, 30, 31, 32, 48] {
let ones = vec![Word::MAX; words];
let a = UBig::from_words(&ones);
assert_eq!(a.sqr(), &a * &a, "all-ones mismatch at words={words}");
let mut hi: Vec<Word> = vec![0; words];
hi[words - 1] = Word::MAX;
let a = UBig::from_words(&hi);
assert_eq!(a.sqr(), &a * &a, "single-high-bit mismatch at words={words}");
let mixed: Vec<Word> = (0..words)
.map(|k| {
if k % 2 == 0 {
Word::MAX
} else {
Word::from(1u8)
}
})
.collect();
let a = UBig::from_words(&mixed);
assert_eq!(a.sqr(), &a * &a, "mixed mismatch at words={words}");
}
}
}