pub fn exact_sum_parts(values: &[f64]) -> Option<(i128, i32)> {
if values.is_empty() || values.iter().any(|value| !value.is_finite()) {
return None;
}
let mut parts = Vec::with_capacity(values.len());
let mut minimum = i32::MAX;
for value in values {
let bits = value.to_bits();
let exponent = ((bits >> 52) & 0x7ff) as i32;
let mut mantissa = bits & ((1_u64 << 52) - 1);
let mut power = if exponent == 0 {
-1074
} else {
mantissa |= 1_u64 << 52;
exponent - 1023 - 52
};
if mantissa == 0 {
continue;
}
let shift = mantissa.trailing_zeros();
mantissa >>= shift;
power += shift as i32;
minimum = minimum.min(power);
let mantissa = if bits >> 63 == 0 {
mantissa as i128
} else {
-(mantissa as i128)
};
parts.push((mantissa, power));
}
if parts.is_empty() {
return Some((0, 0));
}
let mut total = 0_i128;
for (mantissa, power) in parts {
let shift = (power - minimum) as u32;
if shift > 126 {
return None;
}
total = total.checked_add(mantissa.checked_mul(1_i128 << shift)?)?;
}
Some((total, minimum))
}
#[cfg(test)]
mod tests {
#[test]
fn exact_parts_and_fallback() {
assert_eq!(super::exact_sum_parts(&[1.0, 2.0, 3.0]), Some((6, 0)));
assert_eq!(super::exact_sum_parts(&[1.0, -1.0]), Some((0, 0)));
assert_eq!(super::exact_sum_parts(&[-0.0, 0.0]), Some((0, 0)));
assert_eq!(
super::exact_sum_parts(&[f64::from_bits(1), f64::from_bits(1)]),
Some((2, -1074))
);
assert!(super::exact_sum_parts(&[1e308, 1e308]).is_some());
assert!(super::exact_sum_parts(&[1e308, 1e-308]).is_none());
assert!(super::exact_sum_parts(&[f64::NAN]).is_none());
assert!(super::exact_sum_parts(&[]).is_none());
}
}