use crate::error::{CoreError, ErrorContext};
pub fn ulp_distance_f32(a: f32, b: f32) -> Option<u32> {
if a.is_nan() || b.is_nan() {
return None;
}
let bits_a = sign_magnitude_f32(a);
let bits_b = sign_magnitude_f32(b);
Some(bits_a.abs_diff(bits_b))
}
pub fn ulp_distance_f64(a: f64, b: f64) -> Option<u64> {
if a.is_nan() || b.is_nan() {
return None;
}
let bits_a = sign_magnitude_f64(a);
let bits_b = sign_magnitude_f64(b);
Some(bits_a.abs_diff(bits_b))
}
pub fn next_up_f64(x: f64) -> f64 {
if x.is_nan() {
return x;
}
if x == f64::INFINITY {
return f64::INFINITY;
}
if x == 0.0_f64 {
return f64::from_bits(1u64);
}
let bits = x.to_bits();
if x > 0.0 {
f64::from_bits(bits + 1)
} else {
f64::from_bits(bits - 1)
}
}
pub fn next_down_f64(x: f64) -> f64 {
if x.is_nan() {
return x;
}
if x == f64::NEG_INFINITY {
return f64::NEG_INFINITY;
}
if x == 0.0_f64 {
return f64::from_bits(0x8000_0000_0000_0001u64); }
let bits = x.to_bits();
if x < 0.0 {
f64::from_bits(bits + 1)
} else {
f64::from_bits(bits - 1)
}
}
pub fn eps_of(x: f64) -> Result<f64, CoreError> {
if x.is_nan() {
return Err(CoreError::DomainError(ErrorContext::new(
"eps_of: argument is NaN".to_string(),
)));
}
if x.is_infinite() {
return Err(CoreError::DomainError(ErrorContext::new(
"eps_of: argument is infinite".to_string(),
)));
}
let up = next_up_f64(x.abs());
Ok((up - x.abs()) / 2.0)
}
pub fn relative_error(computed: f64, exact: f64) -> Option<f64> {
if computed.is_nan() || exact.is_nan() {
return None;
}
let diff = (computed - exact).abs();
if exact == 0.0 {
if diff == 0.0 {
return Some(0.0);
}
return Some(f64::INFINITY);
}
let ulp = next_up_f64(exact.abs()) - exact.abs();
if ulp == 0.0 {
return Some(0.0);
}
Some(diff / ulp)
}
pub fn float_nearly_equal(a: f64, b: f64, ulp_tolerance: u64, abs_tolerance: f64) -> bool {
if a.is_nan() || b.is_nan() {
return false;
}
if a.is_infinite() || b.is_infinite() {
return a == b;
}
if (a - b).abs() <= abs_tolerance {
return true;
}
match ulp_distance_f64(a, b) {
Some(d) => d <= ulp_tolerance,
None => false,
}
}
pub fn significant_bits(computed: f64, reference: f64) -> Result<u32, CoreError> {
if computed.is_nan() || reference.is_nan() {
return Err(CoreError::DomainError(ErrorContext::new(
"significant_bits: NaN argument".to_string(),
)));
}
if computed == reference {
return Ok(52);
}
if reference == 0.0 {
if computed == 0.0 {
return Ok(52);
}
return Err(CoreError::DomainError(ErrorContext::new(
"significant_bits: reference is zero, computed is non-zero (undefined)".to_string(),
)));
}
let ulp = next_up_f64(reference.abs()) - reference.abs();
if ulp == 0.0 {
return Ok(52);
}
let diff = (computed - reference).abs();
let ratio = diff / ulp;
if ratio <= 0.0 {
return Ok(52);
}
let log2_ratio = ratio.log2().floor() as i64;
let sig = 52i64 - log2_ratio;
Ok(sig.max(0) as u32)
}
#[inline]
fn sign_magnitude_f32(x: f32) -> i32 {
let bits = x.to_bits() as i32;
if bits < 0 {
i32::MIN ^ bits
} else {
bits
}
}
#[inline]
fn sign_magnitude_f64(x: f64) -> i64 {
let bits = x.to_bits() as i64;
if bits < 0 {
i64::MIN ^ bits
} else {
bits
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ulp_distance_same_value() {
assert_eq!(ulp_distance_f64(1.0, 1.0), Some(0));
assert_eq!(ulp_distance_f32(1.0, 1.0), Some(0));
}
#[test]
fn ulp_distance_adjacent_f64() {
let x = 1.0_f64;
let y = next_up_f64(x);
assert_eq!(ulp_distance_f64(x, y), Some(1));
}
#[test]
fn ulp_distance_nan_returns_none() {
assert_eq!(ulp_distance_f64(f64::NAN, 1.0), None);
assert_eq!(ulp_distance_f64(1.0, f64::NAN), None);
}
#[test]
fn next_up_positive() {
let x = 1.0_f64;
let y = next_up_f64(x);
assert!(y > x);
assert_eq!(y - x, f64::EPSILON);
}
#[test]
fn next_down_positive() {
let x = 1.0_f64;
let y = next_down_f64(x);
assert!(y < x);
}
#[test]
fn next_up_zero() {
let y = next_up_f64(0.0);
assert!(y > 0.0);
assert!(y.is_subnormal());
}
#[test]
fn eps_of_one() {
let eps = eps_of(1.0).expect("valid");
assert!((eps - f64::EPSILON / 2.0).abs() < 1e-320);
}
#[test]
fn eps_of_nan_errors() {
assert!(eps_of(f64::NAN).is_err());
}
#[test]
fn relative_error_exact() {
let err = relative_error(1.0, 1.0).expect("should be some");
assert_eq!(err, 0.0);
}
#[test]
fn float_nearly_equal_same() {
assert!(float_nearly_equal(1.0, 1.0, 0, 0.0));
}
#[test]
fn float_nearly_equal_adjacent() {
let x = 1.0_f64;
let y = next_up_f64(x);
assert!(float_nearly_equal(x, y, 1, 0.0));
assert!(!float_nearly_equal(x, y, 0, 0.0));
}
#[test]
fn float_nearly_equal_nan_false() {
assert!(!float_nearly_equal(f64::NAN, 1.0, 10, 0.0));
}
#[test]
fn significant_bits_exact() {
assert_eq!(significant_bits(1.0, 1.0).expect("ok"), 52);
}
#[test]
fn significant_bits_half_eps() {
let x = 1.0_f64;
let y = next_up_f64(x);
let bits = significant_bits(y, x).expect("ok");
assert!(bits <= 52);
}
#[test]
fn ulp_distance_across_zero() {
let a = f64::from_bits(1u64); let b = -f64::from_bits(1u64);
assert_eq!(ulp_distance_f64(a, b), Some(2));
}
}