#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FloatType {
F32,
F64,
}
impl FloatType {
#[inline]
#[must_use]
pub fn epsilon(self) -> f64 {
match self {
FloatType::F32 => f32::EPSILON as f64,
FloatType::F64 => f64::EPSILON,
}
}
#[inline]
#[must_use]
pub fn unit_roundoff(self) -> f64 {
self.epsilon() / 2.0
}
}
#[must_use]
pub fn machine_epsilon_f32() -> f32 {
let mut eps = 1.0_f32;
loop {
let half = eps / 2.0;
if 1.0_f32 + half == 1.0_f32 {
break;
}
eps = half;
}
eps
}
#[must_use]
pub fn machine_epsilon_f64() -> f64 {
let mut eps = 1.0_f64;
loop {
let half = eps / 2.0;
if 1.0_f64 + half == 1.0_f64 {
break;
}
eps = half;
}
eps
}
#[must_use]
pub fn rounding_error_bound(n: usize, dtype: FloatType) -> f64 {
let u = dtype.unit_roundoff();
let nu = n as f64 * u;
let denom = 1.0 - nu;
if denom <= 0.0 {
f64::INFINITY
} else {
nu / denom
}
}
#[inline]
#[must_use]
pub fn significand_bits(val: f64) -> u64 {
const MANTISSA_MASK: u64 = (1u64 << 52) - 1;
val.to_bits() & MANTISSA_MASK
}
#[must_use]
pub fn ulp(val: f64) -> f64 {
if val.is_nan() || val.is_infinite() {
return f64::NAN;
}
if val == 0.0 {
return f64::from_bits(1);
}
let abs_val = val.abs();
let bits = abs_val.to_bits();
let next = f64::from_bits(bits + 1);
next - abs_val
}
#[must_use]
pub fn round_to_significant(val: f64, sig_digits: usize) -> f64 {
if !val.is_finite() {
return val;
}
if val == 0.0 || sig_digits == 0 {
return 0.0;
}
let mag = val.abs().log10().floor();
let scale = 10.0_f64.powf(mag - (sig_digits as f64 - 1.0));
(val / scale).round() * scale
}
#[inline]
#[must_use]
pub fn safe_divide(a: f64, b: f64, default: f64) -> f64 {
if b == 0.0 || !b.is_finite() {
default
} else {
a / b
}
}
#[must_use]
pub fn kahan_sum(values: &[f64]) -> f64 {
let mut sum = 0.0_f64;
let mut c = 0.0_f64; for &v in values {
let y = v - c;
let t = sum + y;
c = (t - sum) - y;
sum = t;
}
sum
}
#[must_use]
pub fn pairwise_sum(values: &[f64]) -> f64 {
pairwise_sum_inner(values)
}
const LEAF_SIZE: usize = 8;
fn pairwise_sum_inner(values: &[f64]) -> f64 {
let n = values.len();
if n == 0 {
return 0.0;
}
if n == 1 {
return values[0];
}
if n <= LEAF_SIZE {
let mut s = 0.0_f64;
for &v in values {
s += v;
}
return s;
}
let mid = n / 2;
pairwise_sum_inner(&values[..mid]) + pairwise_sum_inner(&values[mid..])
}
#[must_use]
pub fn neumaier_sum(values: &[f64]) -> f64 {
let mut sum = 0.0_f64;
let mut c = 0.0_f64;
for &v in values {
let t = sum + v;
if sum.abs() >= v.abs() {
c += (sum - t) + v;
} else {
c += (v - t) + sum;
}
sum = t;
}
sum + c
}
pub struct FloatingPointUtils;
impl FloatingPointUtils {
#[inline]
#[must_use]
pub fn machine_epsilon_f32() -> f32 {
machine_epsilon_f32()
}
#[inline]
#[must_use]
pub fn machine_epsilon_f64() -> f64 {
machine_epsilon_f64()
}
#[inline]
#[must_use]
pub fn rounding_error_bound(n: usize, dtype: FloatType) -> f64 {
rounding_error_bound(n, dtype)
}
#[inline]
#[must_use]
pub fn significand_bits(val: f64) -> u64 {
significand_bits(val)
}
#[inline]
#[must_use]
pub fn ulp(val: f64) -> f64 {
ulp(val)
}
#[inline]
#[must_use]
pub fn round_to_significant(val: f64, sig_digits: usize) -> f64 {
round_to_significant(val, sig_digits)
}
#[inline]
#[must_use]
pub fn safe_divide(a: f64, b: f64, default: f64) -> f64 {
safe_divide(a, b, default)
}
#[inline]
#[must_use]
pub fn kahan_sum(values: &[f64]) -> f64 {
kahan_sum(values)
}
#[inline]
#[must_use]
pub fn pairwise_sum(values: &[f64]) -> f64 {
pairwise_sum(values)
}
#[inline]
#[must_use]
pub fn neumaier_sum(values: &[f64]) -> f64 {
neumaier_sum(values)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_machine_epsilon_f32() {
let eps = machine_epsilon_f32();
assert_eq!(eps, f32::EPSILON);
}
#[test]
fn test_machine_epsilon_f64() {
let eps = machine_epsilon_f64();
assert_eq!(eps, f64::EPSILON);
}
#[test]
fn test_float_type_epsilon() {
assert!((FloatType::F32.epsilon() - f32::EPSILON as f64).abs() < 1e-30);
assert!((FloatType::F64.epsilon() - f64::EPSILON).abs() < 1e-300);
}
#[test]
fn test_float_type_unit_roundoff() {
assert_eq!(FloatType::F64.unit_roundoff(), f64::EPSILON / 2.0);
}
#[test]
fn test_rounding_error_bound_zero_ops() {
let b = rounding_error_bound(0, FloatType::F64);
assert_eq!(b, 0.0);
}
#[test]
fn test_rounding_error_bound_reasonable() {
let b = rounding_error_bound(1000, FloatType::F64);
assert!(b > 0.0);
assert!(b < 1e-10);
}
#[test]
fn test_significand_bits_one() {
assert_eq!(significand_bits(1.0), 0u64);
}
#[test]
fn test_significand_bits_one_point_five() {
let bits = significand_bits(1.5);
assert_eq!(bits, 1u64 << 51);
}
#[test]
fn test_significand_bits_negative_same_as_positive() {
assert_eq!(significand_bits(2.5), significand_bits(-2.5));
}
#[test]
fn test_ulp_one() {
assert_eq!(ulp(1.0), f64::EPSILON);
}
#[test]
fn test_ulp_two() {
assert_eq!(ulp(2.0), 2.0 * f64::EPSILON);
}
#[test]
fn test_ulp_zero() {
let u = ulp(0.0);
assert!(u > 0.0);
assert!(u < f64::MIN_POSITIVE);
}
#[test]
fn test_ulp_nan() {
assert!(ulp(f64::NAN).is_nan());
}
#[test]
fn test_ulp_inf() {
assert!(ulp(f64::INFINITY).is_nan());
}
#[test]
fn test_round_to_significant_pi() {
let v = round_to_significant(std::f64::consts::PI, 4);
assert!((v - 3.142).abs() < 1e-9);
}
#[test]
fn test_round_to_significant_zero() {
assert_eq!(round_to_significant(0.0, 5), 0.0);
}
#[test]
fn test_round_to_significant_zero_digits() {
assert_eq!(round_to_significant(3.14, 0), 0.0);
}
#[test]
fn test_round_to_significant_large() {
let v = round_to_significant(123456.0, 3);
assert!((v - 123000.0).abs() < 1.0);
}
#[test]
fn test_round_to_significant_nan_inf() {
assert!(round_to_significant(f64::NAN, 3).is_nan());
assert!(round_to_significant(f64::INFINITY, 3).is_infinite());
}
#[test]
fn test_safe_divide_normal() {
assert_eq!(safe_divide(10.0, 2.0, 0.0), 5.0);
}
#[test]
fn test_safe_divide_by_zero() {
assert_eq!(safe_divide(1.0, 0.0, 42.0), 42.0);
}
#[test]
fn test_safe_divide_by_nan() {
assert_eq!(safe_divide(1.0, f64::NAN, -1.0), -1.0);
}
#[test]
fn test_safe_divide_by_inf() {
assert_eq!(safe_divide(1.0, f64::INFINITY, 0.5), 0.5);
}
#[test]
fn test_kahan_sum_empty() {
assert_eq!(kahan_sum(&[]), 0.0);
}
#[test]
fn test_kahan_sum_basic() {
assert_eq!(kahan_sum(&[1.0, 2.0, 3.0, 4.0]), 10.0);
}
#[test]
fn test_kahan_sum_cancellation() {
let values = [1e15_f64, 1.0, -1e15];
let s = kahan_sum(&values);
assert_eq!(s, 1.0, "Kahan should handle catastrophic cancellation");
}
#[test]
fn test_kahan_sum_many_ones() {
let ones = vec![1.0_f64; 100_000];
let s = kahan_sum(&ones);
assert!((s - 100_000.0).abs() < 1.0);
}
#[test]
fn test_pairwise_sum_empty() {
assert_eq!(pairwise_sum(&[]), 0.0);
}
#[test]
fn test_pairwise_sum_single() {
assert_eq!(pairwise_sum(&[42.0]), 42.0);
}
#[test]
fn test_pairwise_sum_basic() {
assert_eq!(pairwise_sum(&[1.0, 2.0, 3.0, 4.0, 5.0]), 15.0);
}
#[test]
fn test_pairwise_sum_large() {
let values: Vec<f64> = (1..=1000).map(|i| i as f64).collect();
let s = pairwise_sum(&values);
assert!((s - 500_500.0).abs() < 1.0);
}
#[test]
fn test_neumaier_sum_exact() {
let values: Vec<f64> = (1..=100).map(|i| i as f64).collect();
let s = neumaier_sum(&values);
assert_eq!(s, 5050.0);
}
#[test]
fn test_neumaier_sum_large_small_mix() {
let values = [1e16_f64, 1.0, -1e16, 2.0];
let s = neumaier_sum(&values);
assert!((s - 3.0).abs() < 2.0, "neumaier_sum got {s}, expected ~3.0");
}
#[test]
fn test_utils_struct_delegations() {
assert_eq!(
FloatingPointUtils::machine_epsilon_f64(),
f64::EPSILON
);
assert_eq!(
FloatingPointUtils::significand_bits(1.0),
significand_bits(1.0)
);
assert_eq!(
FloatingPointUtils::ulp(1.0),
f64::EPSILON
);
assert_eq!(
FloatingPointUtils::safe_divide(6.0, 3.0, 0.0),
2.0
);
assert_eq!(
FloatingPointUtils::kahan_sum(&[1.0, 2.0, 3.0]),
6.0
);
assert_eq!(
FloatingPointUtils::pairwise_sum(&[1.0, 2.0, 3.0]),
6.0
);
}
}