use crate::scalar::{FiniteReal, NonZeroReal};
pub(crate) fn finite_dot<const N: usize>(
coefficients: [f64; N],
components: [f64; N],
) -> Result<FiniteReal, f64> {
let products = std::array::from_fn(|index| coefficients[index] * components[index]);
let plain = || {
products
.into_iter()
.reduce(|sum, product| sum + product)
.unwrap_or(0.0)
};
if coefficients
.iter()
.chain(&components)
.any(|value| !value.is_finite())
{
return Err(plain());
}
if let Some(value) = fast_dot(coefficients, components, products) {
return Ok(value);
}
let mut sum = ExactSignedSum::default();
for (coefficient, component) in coefficients.into_iter().zip(components) {
sum.add_product(coefficient, component);
}
sum.finite_sum().ok_or_else(plain)
}
const MIN_SIGNIFICAND_EXPONENT: i32 = -1074;
const EXACT_PRODUCT_EXPONENT: i32 = 4 * MIN_SIGNIFICAND_EXPONENT - 64;
const EXACT_SUM_WORDS: usize = 138;
const EXACT_SUM_WORDS_I32: i32 = 138;
const SUBNORMAL_UNIT_BIT: usize = 3286;
#[derive(Clone, Copy)]
pub(crate) struct ExactSignedSum {
positive: [u64; EXACT_SUM_WORDS],
negative: [u64; EXACT_SUM_WORDS],
invalid: bool,
}
impl Default for ExactSignedSum {
fn default() -> Self {
Self {
positive: [0; EXACT_SUM_WORDS],
negative: [0; EXACT_SUM_WORDS],
invalid: false,
}
}
}
const MAX_BIASED_SIGNIFICAND_EXPONENT: i32 = 0x7fe - 1;
const MAX_SIGNIFICAND_BITS: i32 = 53;
const MIN_SCALED_EXPONENT: i32 = EXACT_PRODUCT_EXPONENT + 1;
const MAX_SCALED_EXPONENT: i32 = EXACT_PRODUCT_EXPONENT + EXACT_SUM_WORDS_I32 * 64 + 1;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct ScaledExponent(i32);
const _: () = assert!(MIN_SIGNIFICAND_EXPONENT + 1 >= MIN_SCALED_EXPONENT);
const _: () = assert!(
MIN_SIGNIFICAND_EXPONENT + MAX_BIASED_SIGNIFICAND_EXPONENT + MAX_SIGNIFICAND_BITS
<= MAX_SCALED_EXPONENT
);
const _: () = assert!(EXACT_PRODUCT_EXPONENT + 1 >= MIN_SCALED_EXPONENT);
const _: () =
assert!(EXACT_PRODUCT_EXPONENT + (EXACT_SUM_WORDS_I32 * 64 - 1) + 2 <= MAX_SCALED_EXPONENT);
impl ScaledExponent {
fn difference(self, other: Self) -> i32 {
self.0 - other.0
}
}
#[derive(Clone, Copy)]
pub(crate) struct ScaledValue {
sign: f64,
mantissa: f64,
pub(crate) exponent: ScaledExponent,
}
impl ScaledValue {
pub(crate) fn doubled(self) -> Self {
Self {
exponent: ScaledExponent(self.exponent.0 + 1),
..self
}
}
pub(crate) fn exponent(self) -> i32 {
self.exponent.0
}
pub(crate) fn rescale(self, exponent: i32) -> Option<FiniteReal> {
super::scale_power_of_two(
self.sign * self.mantissa,
self.exponent.0.checked_sub(exponent)?,
)
}
pub(crate) fn scaled_by(self, scale_exponent: ScaledExponent) -> f64 {
super::scale_power_of_two(
self.sign * self.mantissa,
self.exponent.difference(scale_exponent),
)
.map_or(self.sign * f64::INFINITY, FiniteReal::get)
}
pub(crate) fn finite(self) -> Result<FiniteReal, f64> {
super::scale_power_of_two(self.sign * self.mantissa, self.exponent.0)
.ok_or(self.sign * f64::INFINITY)
}
pub(crate) fn quotient(self, denominator: Self) -> Result<FiniteReal, f64> {
let ratio = self.sign * denominator.sign * (self.mantissa / denominator.mantissa);
super::scale_power_of_two(ratio, self.exponent.difference(denominator.exponent))
.ok_or(ratio.signum() * f64::INFINITY)
}
pub(crate) fn quotient_by_factors<const N: usize>(
self,
denominators: [Self; N],
doubled: bool,
) -> Result<FiniteReal, f64> {
const { assert!(N <= 5) };
let mut mantissa = self.sign * self.mantissa;
let mut exponent = self.exponent.0 + i32::from(doubled);
for denominator in denominators {
mantissa /= denominator.sign * denominator.mantissa;
exponent -= denominator.exponent.0;
}
super::scale_power_of_two(mantissa, exponent).ok_or(mantissa.signum() * f64::INFINITY)
}
pub(crate) fn quotient_shifted_product(self, denominator: Self, exponent_shift: i32) -> f64 {
super::power_of_two_product(
self.sign * denominator.sign * (self.mantissa / denominator.mantissa),
self.exponent.difference(denominator.exponent) + exponent_shift,
)
}
pub(crate) fn of_nonzero(value: NonZeroReal) -> Self {
const RAISE: i32 = 64;
let value = value.get();
let (normal, raised) = if value.abs() < f64::MIN_POSITIVE {
(value * 2.0_f64.powi(RAISE), RAISE)
} else {
(value, 0)
};
let bits = normal.to_bits();
let field_bits = ((bits >> 52) & 0x7ff).to_le_bytes();
let field = i32::from(u16::from_le_bytes([field_bits[0], field_bits[1]]));
let significand = (1_u64 << 52) | (bits & ((1_u64 << 52) - 1));
Self {
sign: if bits >> 63 != 0 { -1.0 } else { 1.0 },
mantissa: f64::from_bits((1022_u64 << 52) | (significand & ((1_u64 << 52) - 1))),
exponent: ScaledExponent(
field - 1 + MIN_SIGNIFICAND_EXPONENT + MAX_SIGNIFICAND_BITS - raised,
),
}
}
pub(crate) fn product_of_nonzero<const N: usize>(factors: [NonZeroReal; N]) -> Option<Self> {
let mut sum = ExactSignedSum::default();
sum.add_factors(factors.map(NonZeroReal::get));
if sum.invalid {
return None;
}
let negative = factors
.iter()
.fold(false, |negative, factor| negative ^ (factor.get() < 0.0));
let magnitude = if negative { sum.negative } else { sum.positive };
let highest_bit =
magnitude
.iter()
.enumerate()
.try_fold(0_u16, |highest, (word, value)| {
if *value == 0 {
Some(highest)
} else {
u16::try_from(
word * 64 + 63
- cadmpeg_core::decode::index_from_u32(value.leading_zeros()),
)
.ok()
}
})?;
rounded_magnitude(negative, &magnitude, highest_bit)
}
}
fn finite_significand(value: f64) -> Option<(bool, u64, u16)> {
if !value.is_finite() {
return None;
}
let bits = value.to_bits();
let negative = bits >> 63 != 0;
let exponent_field = u16::try_from((bits >> 52) & 0x7ff).ok()?;
let fraction = bits & ((1_u64 << 52) - 1);
if exponent_field == 0 {
(fraction != 0).then_some((negative, fraction, 0))
} else {
Some((negative, (1_u64 << 52) | fraction, exponent_field - 1))
}
}
fn add_word(words: &mut [u64; EXACT_SUM_WORDS], index: usize, value: u64) {
let (sum, mut carry) = words[index].overflowing_add(value);
words[index] = sum;
let mut index = index + 1;
while carry {
let (sum, next) = words[index].overflowing_add(1);
words[index] = sum;
carry = next;
index += 1;
}
}
fn add_shifted(words: &mut [u64; EXACT_SUM_WORDS], value: u128, shift: usize) {
let bytes = value.to_le_bytes();
let low = u64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]);
let high = u64::from_le_bytes([
bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
]);
for (limb_index, limb) in [low, high].into_iter().enumerate() {
if limb == 0 {
continue;
}
let bit = shift + limb_index * 64;
let word = bit / 64;
let remainder = bit % 64;
if remainder == 0 {
add_word(words, word, limb);
} else {
add_word(words, word, limb << remainder);
add_word(words, word + 1, limb >> (64 - remainder));
}
}
}
impl ExactSignedSum {
pub(crate) fn add_product(&mut self, left: f64, right: f64) {
self.add_factors([left, right]);
}
pub(crate) fn add_factors<const N: usize>(&mut self, factors: [f64; N]) {
assert!(N <= 4);
let mut product = [1_u64, 0, 0, 0];
let mut negative = false;
let mut exponent = 0_i32;
for factor in factors {
let Some((sign, significand, biased)) = finite_significand(factor) else {
return;
};
negative ^= sign;
exponent += MIN_SIGNIFICAND_EXPONENT + i32::from(biased);
let mut carry = 0_u128;
for word in &mut product {
let value = u128::from(*word) * u128::from(significand) + carry;
let bytes = value.to_le_bytes();
*word = u64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]);
carry = value >> 64;
}
}
let Ok(shift) = usize::try_from(exponent - EXACT_PRODUCT_EXPONENT) else {
self.invalid = true;
return;
};
let target = if negative {
&mut self.negative
} else {
&mut self.positive
};
add_shifted(
target,
u128::from(product[0]) | (u128::from(product[1]) << 64),
shift,
);
add_shifted(
target,
u128::from(product[2]) | (u128::from(product[3]) << 64),
shift + 128,
);
}
pub(crate) fn add_scaled_product(
&mut self,
value: Option<ScaledValue>,
factor: FiniteReal,
) -> Option<()> {
let Some(value) = value else {
return Some(());
};
let Some((negative, significand, exponent)) =
finite_significand(value.sign * value.mantissa)
else {
return Some(());
};
let Some((factor_negative, factor_significand, factor_exponent)) =
finite_significand(factor.get())
else {
return Some(());
};
let product = u128::from(significand) * u128::from(factor_significand);
let shift = value.exponent.0
+ 2 * MIN_SIGNIFICAND_EXPONENT
+ i32::from(exponent)
+ i32::from(factor_exponent)
- EXACT_PRODUCT_EXPONENT;
let shift = usize::try_from(shift).ok()?;
if shift + 128 >= EXACT_SUM_WORDS * 64 {
return None;
}
let target = if negative ^ factor_negative {
&mut self.negative
} else {
&mut self.positive
};
add_shifted(target, product, shift);
Some(())
}
pub(crate) fn finish(self) -> Option<ScaledValue> {
if self.invalid {
return None;
}
let (negative, magnitude) = signed_difference(&self.positive, &self.negative)?;
let word = magnitude.iter().rposition(|value| *value != 0)?;
let highest_bit = u16::try_from(
word * 64 + cadmpeg_core::decode::index_from_u32(magnitude[word].checked_ilog2()?),
)
.ok()?;
rounded_magnitude(negative, &magnitude, highest_bit)
}
pub(crate) fn finite_sum(self) -> Option<FiniteReal> {
if self.invalid {
return None;
}
let Some((negative, magnitude)) = signed_difference(&self.positive, &self.negative) else {
return Some(FiniteReal::ZERO);
};
let word = magnitude.iter().rposition(|value| *value != 0)?;
let highest_bit =
word * 64 + cadmpeg_core::decode::index_from_u32(magnitude[word].checked_ilog2()?);
if highest_bit >= SUBNORMAL_UNIT_BIT + 52 {
return self.finish()?.finite().ok();
}
let mut units = 0_u64;
if highest_bit >= SUBNORMAL_UNIT_BIT {
for bit in (SUBNORMAL_UNIT_BIT..=highest_bit).rev() {
units = (units << 1) | u64::from(bit_is_set(&magnitude, bit));
}
}
let guard = bit_is_set(&magnitude, SUBNORMAL_UNIT_BIT - 1);
let sticky = (0..SUBNORMAL_UNIT_BIT - 1).any(|bit| bit_is_set(&magnitude, bit));
if guard && (sticky || units & 1 != 0) {
units += 1;
}
if units == 0 {
return Some(FiniteReal::ZERO);
}
FiniteReal::subnormal_units(negative, units)
}
}
fn signed_difference(
positive: &[u64; EXACT_SUM_WORDS],
negative: &[u64; EXACT_SUM_WORDS],
) -> Option<(bool, [u64; EXACT_SUM_WORDS])> {
let (larger, smaller, is_negative) =
positive
.iter()
.zip(negative)
.rev()
.find_map(|(left, right)| match left.cmp(right) {
std::cmp::Ordering::Greater => Some((positive, negative, false)),
std::cmp::Ordering::Less => Some((negative, positive, true)),
std::cmp::Ordering::Equal => None,
})?;
let mut magnitude = [0; EXACT_SUM_WORDS];
let mut borrow = false;
for index in 0..EXACT_SUM_WORDS {
let (difference, first_borrow) = larger[index].overflowing_sub(smaller[index]);
let (difference, second_borrow) = difference.overflowing_sub(u64::from(borrow));
magnitude[index] = difference;
borrow = first_borrow || second_borrow;
}
Some((is_negative, magnitude))
}
fn bit_is_set(words: &[u64; EXACT_SUM_WORDS], bit: usize) -> bool {
words[bit / 64] & (1_u64 << (bit % 64)) != 0
}
fn rounded_magnitude(
negative: bool,
magnitude: &[u64; EXACT_SUM_WORDS],
highest_bit: u16,
) -> Option<ScaledValue> {
let keep = (highest_bit + 1).min(53);
let mut significand = 0_u64;
for bit in (highest_bit + 1 - keep..=highest_bit).rev() {
significand = (significand << 1) | u64::from(bit_is_set(magnitude, usize::from(bit)));
}
if keep == 53 {
let guard_bit = highest_bit
.checked_sub(keep)
.is_some_and(|bit| bit_is_set(magnitude, usize::from(bit)));
let sticky = highest_bit.checked_sub(keep).is_some_and(|bit| {
(0..bit).any(|candidate| bit_is_set(magnitude, usize::from(candidate)))
});
if guard_bit && (sticky || significand & 1 != 0) {
significand += 1;
if significand == 1_u64 << 53 {
return Some(ScaledValue {
sign: if negative { -1.0 } else { 1.0 },
mantissa: 0.5,
exponent: ScaledExponent(EXACT_PRODUCT_EXPONENT + i32::from(highest_bit) + 2),
});
}
}
}
Some(ScaledValue {
sign: if negative { -1.0 } else { 1.0 },
mantissa: cadmpeg_core::convert::f64_from_u64(significand)?
* 2.0_f64.powi(-i32::from(keep)),
exponent: ScaledExponent(EXACT_PRODUCT_EXPONENT + i32::from(highest_bit) + 1),
})
}
pub(crate) fn scaled_finite(value: f64) -> Option<ScaledValue> {
NonZeroReal::new(value).map(ScaledValue::of_nonzero)
}
pub(crate) fn scaled_ratio_products<const N: usize>(
value: FiniteReal,
denominator: FiniteReal,
factors: [FiniteReal; N],
) -> Option<[FiniteReal; N]> {
let (Some(value), Some(denominator)) =
(scaled_finite(value.get()), scaled_finite(denominator.get()))
else {
return Some([FiniteReal::ZERO; N]);
};
let ratio = value.sign * denominator.sign * (value.mantissa / denominator.mantissa);
let exponent = value.exponent.difference(denominator.exponent);
let mut products = [FiniteReal::ZERO; N];
for (product, factor) in products.iter_mut().zip(factors) {
let Some(factor) = scaled_finite(factor.get()) else {
continue;
};
*product = super::scale_power_of_two(
ratio * factor.sign * factor.mantissa,
exponent + factor.exponent.0,
)?;
}
Some(products)
}
pub(crate) fn fast_dot<const N: usize>(
coefficients: [f64; N],
components: [f64; N],
products: [f64; N],
) -> Option<FiniteReal> {
if products.iter().any(|product| !product.is_finite()) {
return None;
}
if coefficients.into_iter().zip(components).zip(products).any(
|((coefficient, component), product)| {
coefficient != 0.0 && component != 0.0 && product.abs() < f64::MIN_POSITIVE
},
) {
return None;
}
let scale = products
.iter()
.fold(0.0_f64, |scale, product| scale.max(product.abs()));
if scale == 0.0 {
return Some(FiniteReal::ZERO);
}
let sum = FiniteReal::new(products.into_iter().sum::<f64>())?;
if sum.get().abs() / scale < f64::EPSILON {
return None;
}
Some(sum)
}
pub(crate) enum ProductSum {
Undefined,
Zero,
Value(ScaledValue),
}
impl ProductSum {
fn from_scaled(value: Option<ScaledValue>) -> Self {
match value {
Some(value) => Self::Value(value),
None => Self::Zero,
}
}
}
pub(crate) fn product_sum<const N: usize>(
terms: impl Iterator<Item = Option<[f64; N]>> + Clone,
) -> ProductSum {
let mut sum = 0.0_f64;
let mut largest = 0.0_f64;
let mut exact = false;
for factors in terms.clone() {
let Some(factors) = factors else {
return ProductSum::Undefined;
};
if factors.iter().any(|value| !value.is_finite()) {
return ProductSum::Undefined;
}
if factors.contains(&0.0) {
continue;
}
let mut product = 1.0;
for factor in factors {
product *= factor;
exact |= !product.is_normal();
}
sum += product;
largest = largest.max(product.abs());
exact |= !sum.is_finite();
}
exact |= largest != 0.0 && sum.abs() / largest < f64::EPSILON;
if !exact {
return ProductSum::from_scaled(scaled_finite(sum));
}
let mut sum = ExactSignedSum::default();
for factors in terms {
let Some(factors) = factors else {
return ProductSum::Undefined;
};
sum.add_factors(factors);
}
ProductSum::from_scaled(sum.finish())
}
#[cfg(test)]
mod tests;