use crate::SumF64;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DotError {
ExactnessLost,
}
impl core::fmt::Display for DotError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"dot product exactness lost: a partial product underflowed to the subnormal range"
)
}
}
impl std::error::Error for DotError {}
#[derive(Clone, PartialEq, Eq, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DotF64 {
acc: SumF64,
underflowed: bool,
}
impl DotF64 {
#[inline]
pub const fn new() -> Self {
Self {
acc: SumF64::new(),
underflowed: false,
}
}
#[inline]
pub const fn pairs(&self) -> u64 {
self.acc.count() / 2
}
pub fn push(&mut self, a: f64, b: f64) {
let hi = a * b;
let lo = a.mul_add(b, -hi);
if hi.is_infinite() {
self.acc.add(hi);
self.acc.add(0.0); return;
}
if a != 0.0 && b != 0.0 && hi.abs() < f64::MIN_POSITIVE {
self.underflowed = true;
}
self.acc.add(hi);
self.acc.add(lo);
}
pub fn extend_from_slices(&mut self, xs: &[f64], ys: &[f64]) {
assert_eq!(
xs.len(),
ys.len(),
"dot product requires equal-length slices"
);
for (x, y) in xs.iter().zip(ys) {
self.push(*x, *y);
}
}
pub fn merge(&mut self, other: &DotF64) {
self.acc.merge(&other.acc);
self.underflowed |= other.underflowed;
}
#[inline]
pub const fn is_exact(&self) -> bool {
!self.underflowed
}
#[inline]
pub fn value(&self) -> f64 {
self.acc.value()
}
pub fn try_value(&self) -> Result<f64, DotError> {
if self.underflowed {
Err(DotError::ExactnessLost)
} else {
Ok(self.acc.value())
}
}
pub fn to_bytes(&self) -> [u8; SumF64::BYTES + 1] {
let mut out = [0u8; SumF64::BYTES + 1];
out[..SumF64::BYTES].copy_from_slice(&self.acc.to_bytes());
out[SumF64::BYTES] = self.underflowed as u8;
out
}
pub fn from_bytes(b: &[u8; SumF64::BYTES + 1]) -> Option<Self> {
let mut inner = [0u8; SumF64::BYTES];
inner.copy_from_slice(&b[..SumF64::BYTES]);
let acc = SumF64::from_bytes(&inner)?;
let underflowed = match b[SumF64::BYTES] {
0 => false,
1 => true,
_ => return None,
};
Some(Self { acc, underflowed })
}
}
pub fn dot(xs: &[f64], ys: &[f64]) -> Result<f64, DotError> {
let mut d = DotF64::new();
d.extend_from_slices(xs, ys);
d.try_value()
}