#[must_use]
pub fn erf(x: f64) -> f64 {
const A1: f64 = 0.254_829_592;
const A2: f64 = -0.284_496_736;
const A3: f64 = 1.421_413_741;
const A4: f64 = -1.453_152_027;
const A5: f64 = 1.061_405_429;
const P: f64 = 0.327_591_1;
let sign = if x < 0.0 { -1.0 } else { 1.0 };
let x = x.abs();
let t = 1.0 / (1.0 + P * x);
let y = 1.0 - (((((A5 * t + A4) * t) + A3) * t + A2) * t + A1) * t * (-x * x).exp();
sign * y
}
#[must_use]
pub fn erf_f32(x: f32) -> f32 {
erf(f64::from(x)) as f32
}
const SQRPI: f64 = 0.564_189_583_547_756_3;
const CODY_THRESH: f64 = 0.468_75;
const CODY_XBIG: f64 = 26.543;
#[allow(clippy::unreadable_literal)]
const CODY_A: [f64; 5] = [
3.1611237438705655,
113.86415415105016,
377.485237685302,
3209.3775891384694,
0.18577770618460315,
];
#[allow(clippy::unreadable_literal)]
const CODY_B: [f64; 4] = [
23.601290952344122,
244.02463793444417,
1282.6165260773723,
2844.236833439171,
];
#[allow(clippy::unreadable_literal)]
const CODY_C: [f64; 9] = [
0.5641884969886701,
8.883149794388377,
66.11919063714163,
298.6351381974001,
881.952221241769,
1712.0476126340707,
2051.0783778260716,
1230.3393547979972,
2.1531153547440383e-8,
];
#[allow(clippy::unreadable_literal)]
const CODY_D: [f64; 8] = [
15.744926110709835,
117.6939508913125,
537.1811018620099,
1621.3895745666903,
3290.7992357334597,
4362.619090143247,
3439.3676741437216,
1230.3393548037495,
];
#[allow(clippy::unreadable_literal)]
const CODY_P: [f64; 6] = [
0.30532663496123236,
0.36034489994980445,
0.12578172611122926,
0.016083785148742275,
0.0006587491615298378,
0.016315387137302097,
];
#[allow(clippy::unreadable_literal)]
const CODY_Q: [f64; 5] = [
2.568520192289822,
1.8729528499234604,
0.5279051029514285,
0.06051834131244132,
0.0023352049762686918,
];
fn cody_erf_small(x: f64) -> f64 {
let z = x * x;
let mut xnum = CODY_A[4] * z;
let mut xden = z;
for i in 0..3 {
xnum = (xnum + CODY_A[i]) * z;
xden = (xden + CODY_B[i]) * z;
}
x * (xnum + CODY_A[3]) / (xden + CODY_B[3])
}
fn cody_erfc_pos(y: f64) -> f64 {
if y >= CODY_XBIG {
return 0.0;
}
let result = if y <= 4.0 {
let mut xnum = CODY_C[8] * y;
let mut xden = y;
for i in 0..7 {
xnum = (xnum + CODY_C[i]) * y;
xden = (xden + CODY_D[i]) * y;
}
(xnum + CODY_C[7]) / (xden + CODY_D[7])
} else {
let z = 1.0 / (y * y);
let mut xnum = CODY_P[5] * z;
let mut xden = z;
for i in 0..4 {
xnum = (xnum + CODY_P[i]) * z;
xden = (xden + CODY_Q[i]) * z;
}
let r = z * (xnum + CODY_P[4]) / (xden + CODY_Q[4]);
(SQRPI - r) / y
};
let ysq = (y * 16.0).trunc() / 16.0;
let del = (y - ysq) * (y + ysq);
(-ysq * ysq).exp() * (-del).exp() * result
}
#[must_use]
pub fn erf_precise(x: f64) -> f64 {
if x.is_nan() {
return x;
}
let y = x.abs();
if y <= CODY_THRESH {
return cody_erf_small(x);
}
let v = 1.0 - cody_erfc_pos(y);
if x < 0.0 { -v } else { v }
}
#[must_use]
pub fn erfc_precise(x: f64) -> f64 {
if x.is_nan() {
return x;
}
let y = x.abs();
if y <= CODY_THRESH {
return 1.0 - cody_erf_small(x);
}
if x > 0.0 {
cody_erfc_pos(y)
} else {
2.0 - cody_erfc_pos(y)
}
}
#[must_use]
pub fn std_dev(samples: &[f64]) -> f64 {
if samples.len() < 2 {
return 0.0;
}
let n = samples.len() as f64;
let mean = samples.iter().sum::<f64>() / n;
let variance = samples.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (n - 1.0);
variance.sqrt()
}
#[must_use]
pub fn std_dev_f32(samples: &[f32]) -> f32 {
if samples.len() < 2 {
return 0.0;
}
let n = samples.len() as f32;
let mean = samples.iter().sum::<f32>() / n;
let variance = samples.iter().map(|x| (x - mean).powi(2)).sum::<f32>() / (n - 1.0);
variance.sqrt()
}
#[must_use]
pub fn std_dev_with_mean(samples: &[f64], mean: f64) -> f64 {
if samples.len() < 2 {
return 0.0;
}
let variance =
samples.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (samples.len() - 1) as f64;
variance.sqrt()
}
#[must_use]
pub fn std_dev_f32_with_mean(samples: &[f32], mean: f32) -> f32 {
if samples.len() < 2 {
return 0.0;
}
let variance =
samples.iter().map(|x| (x - mean).powi(2)).sum::<f32>() / (samples.len() - 1) as f32;
variance.sqrt()
}
#[must_use]
pub fn cosine_similarity_f32(a: &[f32], b: &[f32]) -> f32 {
let dot: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm_a == 0.0 || norm_b == 0.0 {
return 0.0;
}
dot / (norm_a * norm_b)
}
#[must_use]
pub fn cosine_similarity(a: &[f64], b: &[f64]) -> f64 {
let dot: f64 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
let norm_a: f64 = a.iter().map(|x| x * x).sum::<f64>().sqrt();
let norm_b: f64 = b.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm_a == 0.0 || norm_b == 0.0 {
return 0.0;
}
dot / (norm_a * norm_b)
}
#[must_use]
pub fn usage_percent(used: u64, total: u64) -> f64 {
if total == 0 {
return 0.0;
}
(used as f64 / total as f64) * 100.0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_erf_zero() {
assert!((erf(0.0) - 0.0).abs() < 1e-6);
}
#[test]
fn test_erf_positive() {
assert!((erf(1.0) - 0.842_700_793).abs() < 1e-6);
}
#[test]
fn test_erf_negative_symmetry() {
assert!((erf(-1.0) + erf(1.0)).abs() < 1e-10);
}
fn oracle_erf_series(t: f64) -> f64 {
let mut u = t;
let mut sum = t;
let mut n = 1.0_f64;
while n <= 200.0 {
u *= -(t * t) / n;
let add = u / (2.0 * n + 1.0);
sum += add;
if add == 0.0 || add.abs() < 1e-18 * sum.abs() {
break;
}
n += 1.0;
}
sum * 2.0 / std::f64::consts::PI.sqrt()
}
fn oracle_erfc_cf(t: f64) -> f64 {
let mut cf = 0.0_f64;
let mut k = 80_i32;
while k >= 1 {
cf = (f64::from(k) / 2.0) / (t + cf);
k -= 1;
}
(-t * t).exp() / std::f64::consts::PI.sqrt() / (t + cf)
}
fn oracle_erf(t: f64) -> f64 {
let a = t.abs();
let v = if a <= 2.0 {
oracle_erf_series(a)
} else {
1.0 - oracle_erfc_cf(a)
};
if t < 0.0 { -v } else { v }
}
#[test]
fn erf_precise_matches_independent_oracle_across_the_range() {
let mut worst = 0.0_f64;
let mut worst_at = 0.0_f64;
for i in 0..=1200 {
let x = -6.0 + 0.01 * f64::from(i);
let d = (erf_precise(x) - oracle_erf(x)).abs();
if d > worst {
worst = d;
worst_at = x;
}
}
assert!(
worst < 1e-14,
"erf_precise deviates from the independent oracle by {worst:.3e} at x={worst_at}"
);
}
#[test]
fn erf_precise_is_far_more_accurate_than_the_abramowitz_stegun_erf() {
let mut worst_as = 0.0_f64;
let mut worst_precise = 0.0_f64;
for i in 0..=1200 {
let x = -6.0 + 0.01 * f64::from(i);
let want = oracle_erf(x);
worst_as = worst_as.max((erf(x) - want).abs());
worst_precise = worst_precise.max((erf_precise(x) - want).abs());
}
assert!(
worst_as > 1e-9,
"A&S erf unexpectedly accurate ({worst_as:.3e}) — the premise for erf_precise changed"
);
assert!(
worst_precise * 1e6 < worst_as,
"erf_precise (worst {worst_precise:.3e}) must be orders of magnitude better \
than A&S erf (worst {worst_as:.3e})"
);
}
#[test]
fn erfc_precise_keeps_relative_accuracy_in_the_far_tail() {
for &(x, want) in &[
(2.0_f64, 4.677_734_981_047_265e-3_f64),
(3.0, 2.209_049_699_858_544e-5),
(4.0, 1.541_725_790_028_002_6e-8),
(5.0, 1.537_459_794_428_035_4e-12),
] {
let got = erfc_precise(x);
let rel = (got - want).abs() / want;
assert!(
rel < 1e-13,
"erfc_precise({x}) = {got:e}, expected {want:e} (rel {rel:.3e})"
);
}
}
#[test]
fn erfc_precise_and_erf_precise_are_consistent_and_symmetric() {
for i in 0..=120 {
let x = -6.0 + 0.1 * f64::from(i);
assert!(
(erfc_precise(x) - (1.0 - erf_precise(x))).abs() < 1e-14,
"erfc_precise({x}) inconsistent with 1 - erf_precise({x})"
);
assert!(
(erf_precise(-x) + erf_precise(x)).abs() < 1e-15,
"erf_precise must be odd, failed at {x}"
);
}
assert_eq!(erf_precise(0.0), 0.0);
assert!((erfc_precise(0.0) - 1.0).abs() < 1e-15);
assert!(erf_precise(f64::NAN).is_nan());
assert!(erfc_precise(f64::NAN).is_nan());
assert_eq!(erfc_precise(30.0), 0.0, "erfc underflows to 0 past XBIG");
}
#[test]
fn test_erf_large() {
assert!((erf(5.0) - 1.0).abs() < 1e-6);
}
#[test]
fn test_erf_f32_matches() {
let f32_val = erf_f32(1.0_f32);
let f64_val = erf(1.0) as f32;
assert!((f32_val - f64_val).abs() < 1e-6);
}
#[test]
fn test_std_dev_known_value() {
let data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
assert!((std_dev(&data) - 2.138).abs() < 0.01);
}
#[test]
fn test_std_dev_single_element() {
assert_eq!(std_dev(&[42.0]), 0.0);
}
#[test]
fn test_std_dev_empty() {
assert_eq!(std_dev(&[]), 0.0);
}
#[test]
fn test_std_dev_identical_values() {
assert_eq!(std_dev(&[5.0, 5.0, 5.0, 5.0]), 0.0);
}
#[test]
fn test_std_dev_f32() {
let data: Vec<f32> = vec![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
assert!((std_dev_f32(&data) - 2.138).abs() < 0.02);
}
#[test]
fn test_std_dev_with_mean_matches() {
let data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
let mean = data.iter().sum::<f64>() / data.len() as f64;
let sd1 = std_dev(&data);
let sd2 = std_dev_with_mean(&data, mean);
assert!((sd1 - sd2).abs() < 1e-10);
}
#[test]
fn test_cosine_identical() {
let a = [1.0, 2.0, 3.0];
assert!((cosine_similarity(&a, &a) - 1.0).abs() < 1e-10);
}
#[test]
fn test_cosine_orthogonal() {
let a = [1.0, 0.0, 0.0];
let b = [0.0, 1.0, 0.0];
assert!(cosine_similarity(&a, &b).abs() < 1e-10);
}
#[test]
fn test_cosine_opposite() {
let a = [1.0, 0.0];
let b = [-1.0, 0.0];
assert!((cosine_similarity(&a, &b) + 1.0).abs() < 1e-10);
}
#[test]
fn test_cosine_zero_vector() {
let a = [0.0, 0.0, 0.0];
let b = [1.0, 2.0, 3.0];
assert_eq!(cosine_similarity(&a, &b), 0.0);
}
#[test]
fn test_cosine_f32() {
let a = [1.0f32, 0.0, 0.0];
let b = [0.0f32, 1.0, 0.0];
assert!(cosine_similarity_f32(&a, &b).abs() < 1e-6);
}
#[test]
fn test_usage_percent_normal() {
assert!((usage_percent(750, 1000) - 75.0).abs() < 1e-10);
}
#[test]
fn test_usage_percent_zero_total() {
assert_eq!(usage_percent(0, 0), 0.0);
}
#[test]
fn test_usage_percent_full() {
assert!((usage_percent(1000, 1000) - 100.0).abs() < 1e-10);
}
#[test]
fn test_usage_percent_empty() {
assert!((usage_percent(0, 1000) - 0.0).abs() < 1e-10);
}
}