use super::{Scratch, ScratchExhausted, Span};
use crate::expansion;
impl Scratch<'_> {
pub fn product(&mut self, a: Span, b: Span) -> Result<Span, ScratchExhausted> {
let needed = working_space(a.len, b.len).ok_or(ScratchExhausted)?;
self.write_result([a, b], needed, |[a, b], work| {
let (short, long) = if a.len() <= b.len() { (a, b) } else { (b, a) };
write_product(short, long, work)
})
}
}
fn working_space(la: u32, lb: u32) -> Option<u64> {
if la == 0 || lb == 0 {
return Some(0);
}
let longer = u64::from(la.max(lb));
(u64::from(la) * u64::from(lb))
.checked_mul(4)?
.checked_add(2 * longer)
}
fn write_product(short: &[f64], long: &[f64], work: &mut [f64]) -> usize {
if short.is_empty() {
return 0;
}
let zone = 2 * short.len() * long.len();
let (sums, partial) = work.split_at_mut(2 * zone);
let mut in_first_zone = true;
let mut len = 0;
for &c in short {
let partial_len = expansion::scale(long, c, partial);
let (first, second) = sums.split_at_mut(zone);
let (from, to) = if in_first_zone {
(first, second)
} else {
(second, first)
};
len = expansion::sum(&from[..len], &partial[..partial_len], to);
in_first_zone = !in_first_zone;
}
if !in_first_zone {
sums.copy_within(zone..zone + len, 0);
}
len
}
#[cfg(test)]
mod tests {
use super::working_space;
#[test]
fn working_space_is_exact_and_never_overflows() {
assert_eq!(working_space(0, 7), Some(0));
assert_eq!(working_space(7, 0), Some(0));
assert_eq!(working_space(2, 3), Some(4 * 6 + 2 * 3));
assert_eq!(working_space(u32::MAX, 1), Some(6 * u64::from(u32::MAX)));
assert_eq!(working_space(u32::MAX, u32::MAX), None);
}
}