use crate::core::{Error, Result};
pub struct SafeFloat;
impl SafeFloat {
pub const EPSILON: f64 = 1e-10;
pub fn div(a: f64, b: f64) -> Result<f64> {
if b.abs() < Self::EPSILON {
return Err(Error::DivisionByZero);
}
let result = a / b;
if result.is_nan() || result.is_infinite() {
return Err(Error::Overflow(format!("{} / {} is not finite", a, b)));
}
Ok(result)
}
pub fn div_f32(a: f32, b: f32) -> Result<f32> {
if b.abs() < f32::EPSILON {
return Err(Error::DivisionByZero);
}
let result = a / b;
if result.is_nan() || result.is_infinite() {
return Err(Error::Overflow(format!("{} / {} is not finite", a, b)));
}
Ok(result)
}
pub fn ln(x: f64) -> Result<f64> {
if x <= 0.0 {
return Err(Error::ValidationError(format!(
"ln({}) undefined for non-positive values",
x
)));
}
Ok(x.ln())
}
pub fn log10(x: f64) -> Result<f64> {
if x <= 0.0 {
return Err(Error::ValidationError(format!(
"log10({}) undefined for non-positive values",
x
)));
}
Ok(x.log10())
}
pub fn sqrt(x: f64) -> Result<f64> {
if x < 0.0 {
return Err(Error::ValidationError(format!(
"sqrt({}) undefined for negative values",
x
)));
}
Ok(x.sqrt())
}
pub fn pow(base: f64, exp: f64) -> Result<f64> {
let result = base.powf(exp);
if result.is_nan() || result.is_infinite() {
return Err(Error::Overflow(format!(
"{}^{} is not finite",
base, exp
)));
}
Ok(result)
}
pub fn exp(x: f64) -> Result<f64> {
let result = x.exp();
if result.is_infinite() {
return Err(Error::Overflow(format!("exp({}) overflows", x)));
}
Ok(result)
}
pub fn exp_f32(x: f32) -> Result<f32> {
let result = x.exp();
if result.is_infinite() {
return Err(Error::Overflow(format!("exp({}) overflows", x)));
}
Ok(result)
}
pub fn magnitude(v: &[f64]) -> f64 {
v.iter().map(|x| x * x).sum::<f64>().sqrt()
}
pub fn magnitude_f32(v: &[f32]) -> f32 {
v.iter().map(|x| x * x).sum::<f32>().sqrt()
}
pub fn normalize(v: &[f64]) -> Result<Vec<f64>> {
let mag = Self::magnitude(v);
if mag < Self::EPSILON {
return Err(Error::DivisionByZero);
}
Ok(v.iter().map(|x| x / mag).collect())
}
pub fn normalize_f32(v: &[f32]) -> Result<Vec<f32>> {
let mag = Self::magnitude_f32(v);
if mag < f32::EPSILON {
return Err(Error::DivisionByZero);
}
Ok(v.iter().map(|x| x / mag).collect())
}
pub fn is_finite(x: f64) -> bool {
x.is_finite()
}
pub fn is_safe_divisor(x: f64) -> bool {
x.is_finite() && x.abs() >= Self::EPSILON
}
pub fn clamp(value: f64, min: f64, max: f64) -> f64 {
if value.is_nan() {
return min;
}
if value < min {
min
} else if value > max {
max
} else {
value
}
}
pub fn reciprocal(x: f64) -> Result<f64> {
Self::div(1.0, x)
}
pub fn mean(v: &[f64]) -> Result<f64> {
if v.is_empty() {
return Err(Error::EmptyInput);
}
let sum: f64 = v.iter().sum();
Self::div(sum, v.len() as f64)
}
pub fn mean_f32(v: &[f32]) -> Result<f32> {
if v.is_empty() {
return Err(Error::EmptyInput);
}
let sum: f32 = v.iter().sum();
Self::div_f32(sum, v.len() as f32)
}
pub fn variance(v: &[f64]) -> Result<f64> {
let mean = Self::mean(v)?;
let sum_sq: f64 = v.iter().map(|x| (x - mean).powi(2)).sum();
Self::div(sum_sq, v.len() as f64)
}
pub fn std_dev(v: &[f64]) -> Result<f64> {
let var = Self::variance(v)?;
Self::sqrt(var)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_div_normal() {
assert_eq!(SafeFloat::div(10.0, 2.0).unwrap(), 5.0);
assert_eq!(SafeFloat::div(-10.0, 2.0).unwrap(), -5.0);
}
#[test]
fn test_div_by_zero() {
assert!(SafeFloat::div(1.0, 0.0).is_err());
assert!(SafeFloat::div(0.0, 0.0).is_err());
assert!(SafeFloat::div_f32(1.0, 0.0).is_err());
}
#[test]
fn test_ln_positive() {
let result = SafeFloat::ln(std::f64::consts::E).unwrap();
assert!((result - 1.0).abs() < 1e-10);
}
#[test]
fn test_ln_non_positive() {
assert!(SafeFloat::ln(0.0).is_err());
assert!(SafeFloat::ln(-1.0).is_err());
}
#[test]
fn test_sqrt_positive() {
assert_eq!(SafeFloat::sqrt(4.0).unwrap(), 2.0);
assert_eq!(SafeFloat::sqrt(0.0).unwrap(), 0.0);
}
#[test]
fn test_sqrt_negative() {
assert!(SafeFloat::sqrt(-1.0).is_err());
}
#[test]
fn test_normalize() {
let v = vec![3.0, 4.0];
let n = SafeFloat::normalize(&v).unwrap();
assert!((n[0] - 0.6).abs() < 1e-10);
assert!((n[1] - 0.8).abs() < 1e-10);
let mag = SafeFloat::magnitude(&n);
assert!((mag - 1.0).abs() < 1e-10);
}
#[test]
fn test_normalize_zero_vector() {
let v = vec![0.0, 0.0, 0.0];
assert!(SafeFloat::normalize(&v).is_err());
}
#[test]
fn test_exp_normal() {
assert!((SafeFloat::exp(0.0).unwrap() - 1.0).abs() < 1e-10);
assert!((SafeFloat::exp(1.0).unwrap() - std::f64::consts::E).abs() < 1e-10);
}
#[test]
fn test_exp_overflow() {
assert!(SafeFloat::exp(1000.0).is_err());
}
#[test]
fn test_clamp_nan() {
let nan = f64::NAN;
assert_eq!(SafeFloat::clamp(nan, 0.0, 1.0), 0.0);
}
#[test]
fn test_clamp_normal() {
assert_eq!(SafeFloat::clamp(0.5, 0.0, 1.0), 0.5);
assert_eq!(SafeFloat::clamp(-1.0, 0.0, 1.0), 0.0);
assert_eq!(SafeFloat::clamp(2.0, 0.0, 1.0), 1.0);
}
#[test]
fn test_mean() {
let v = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert_eq!(SafeFloat::mean(&v).unwrap(), 3.0);
}
#[test]
fn test_mean_empty() {
let v: Vec<f64> = vec![];
assert!(SafeFloat::mean(&v).is_err());
}
#[test]
fn test_variance() {
let v = vec![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
let var = SafeFloat::variance(&v).unwrap();
assert!((var - 4.0).abs() < 1e-10);
}
#[test]
fn test_std_dev() {
let v = vec![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
let sd = SafeFloat::std_dev(&v).unwrap();
assert!((sd - 2.0).abs() < 1e-10);
}
}