pub const STABILITY_EPSILON_F32: f32 = 1e-7;
pub const STABILITY_EPSILON_F64: f64 = 1e-15;
pub const MAX_SAFE_VALUE_F32: f32 = 1e30;
pub const MAX_SAFE_VALUE_F64: f64 = 1e300;
#[inline]
pub fn is_stable_f32(x: f32) -> bool {
x.is_finite() && x.abs() < MAX_SAFE_VALUE_F32
}
#[inline]
pub fn is_stable_f64(x: f64) -> bool {
x.is_finite() && x.abs() < MAX_SAFE_VALUE_F64
}
#[inline]
pub fn stabilize_f32(x: f32) -> f32 {
if !x.is_finite() {
return 0.0;
}
if x.abs() > MAX_SAFE_VALUE_F32 {
x.signum() * MAX_SAFE_VALUE_F32
} else {
x
}
}
#[inline]
pub fn stabilize_f64(x: f64) -> f64 {
if !x.is_finite() {
return 0.0;
}
if x.abs() > MAX_SAFE_VALUE_F64 {
x.signum() * MAX_SAFE_VALUE_F64
} else {
x
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn small_magnitudes_are_stable() {
for &x in &[1e-8f32, 1e-20, 1e-30, f32::MIN_POSITIVE, -1e-8, 0.0] {
assert!(is_stable_f32(x), "{x} should be considered stable");
}
for &x in &[1e-16f64, 1e-200, -1e-16, 0.0] {
assert!(is_stable_f64(x), "{x} should be considered stable");
}
}
#[test]
fn genuine_hazards_are_unstable() {
assert!(!is_stable_f32(f32::NAN));
assert!(!is_stable_f32(f32::INFINITY));
assert!(!is_stable_f32(-f32::INFINITY));
assert!(!is_stable_f32(1e31));
assert!(!is_stable_f64(f64::NAN));
assert!(!is_stable_f64(1e301));
}
#[test]
fn stabilize_preserves_small_values() {
assert_eq!(stabilize_f32(1e-30), 1e-30);
assert_eq!(stabilize_f32(-1e-30), -1e-30);
assert_eq!(stabilize_f64(1e-300), 1e-300);
assert_eq!(stabilize_f32(0.0), 0.0);
}
#[test]
fn stabilize_contains_real_hazards() {
assert_eq!(stabilize_f32(f32::NAN), 0.0);
assert_eq!(stabilize_f32(f32::INFINITY), 0.0);
assert_eq!(stabilize_f32(1e31), MAX_SAFE_VALUE_F32);
assert_eq!(stabilize_f64(-1e301), -MAX_SAFE_VALUE_F64);
}
}