const SMALL: f64 = 0.468_75;
const LARGE: f64 = 4.0;
const XBIG: f64 = 26.543;
const INV_SQRT_PI: f64 = 5.641_895_835_477_563e-1;
const A_ERF: [f64; 5] = [
3.161_123_743_870_565_6e0,
1.138_641_541_510_501_6e2,
3.774_852_376_853_02e2,
3.209_377_589_138_469_4e3,
1.857_777_061_846_031_5e-1,
];
const B_ERF: [f64; 4] = [
2.360_129_095_234_412_2e1,
2.440_246_379_344_441_7e2,
1.282_616_526_077_372_3e3,
2.844_236_833_439_171e3,
];
const C_ERFC: [f64; 9] = [
5.641_884_969_886_701e-1,
8.883_149_794_388_377,
6.611_919_063_714_163e1,
2.986_351_381_974_001e2,
8.819_522_212_417_69e2,
1.712_047_612_634_070_7e3,
2.051_078_377_826_071_6e3,
1.230_339_354_797_997_2e3,
2.153_115_354_744_038_3e-8,
];
const D_ERFC: [f64; 8] = [
1.574_492_611_070_983_5e1,
1.176_939_508_913_125e2,
5.371_811_018_620_099e2,
1.621_389_574_566_690_3e3,
3.290_799_235_733_459_7e3,
4.362_619_090_143_247e3,
3.439_367_674_143_721_6e3,
1.230_339_354_803_749_5e3,
];
const P_ASYMP: [f64; 6] = [
3.053_266_349_612_323_6e-1,
3.603_448_999_498_044_5e-1,
1.257_817_261_112_292_6e-1,
1.608_378_514_874_227_5e-2,
6.587_491_615_298_378e-4,
1.631_538_713_730_209_7e-2,
];
const Q_ASYMP: [f64; 5] = [
2.568_520_192_289_822,
1.872_952_849_923_460_4,
5.279_051_029_514_285e-1,
6.051_834_131_244_132e-2,
2.335_204_976_268_691_8e-3,
];
#[must_use]
pub fn erf(x: f64) -> f64 {
let ax = x.abs();
if ax < SMALL {
return erf_small(x);
}
let c = erfc_ge_half(ax);
if x < 0.0 { c - 1.0 } else { 1.0 - c }
}
#[must_use]
pub fn erfc(x: f64) -> f64 {
let ax = x.abs();
if ax < SMALL {
return 1.0 - erf_small(x);
}
let c = erfc_ge_half(ax);
if x < 0.0 { 2.0 - c } else { c }
}
#[must_use]
pub fn ln_erfc(x: f64) -> f64 {
let ax = x.abs();
if ax < SMALL {
return (1.0 - erf_small(x)).ln();
}
if x < 0.0 {
return (2.0 - erfc_ge_half(ax)).ln();
}
ln_erfc_ge_half(ax)
}
fn ln_erfc_ge_half(ax: f64) -> f64 {
if ax.is_infinite() {
return f64::NEG_INFINITY;
}
let log_weight = ln_exp_weight(ax);
let ratio = if ax < LARGE {
cody_ratio(ax, &C_ERFC, &D_ERFC)
} else {
let z = 1.0 / (ax * ax);
let r = z * cody_ratio(z, &P_ASYMP, &Q_ASYMP);
(INV_SQRT_PI - r) / ax
};
log_weight + ratio.ln()
}
fn ln_exp_weight(ax: f64) -> f64 {
let t = trunc_16(ax);
let d = ax - t;
(-d).mul_add(ax + t, -t * t)
}
fn erf_small(x: f64) -> f64 {
let z = x * x;
x * cody_ratio(z, &A_ERF, &B_ERF)
}
fn cody_ratio(v: f64, nums: &[f64], dens: &[f64]) -> f64 {
let empty: &[f64] = &[];
let (seed, rest) = nums.split_last().map_or((0.0, empty), |(s, r)| (*s, r));
let (num_last, num_mid) = rest.split_last().map_or((0.0, empty), |(l, m)| (*l, m));
let (den_last, den_mid) = dens.split_last().map_or((0.0, empty), |(l, m)| (*l, m));
let mut xnum = seed * v;
for &c in num_mid {
xnum = (xnum + c) * v;
}
let mut xden = v;
for &c in den_mid {
xden = (xden + c) * v;
}
(xnum + num_last) / (xden + den_last)
}
fn erfc_ge_half(ax: f64) -> f64 {
if ax >= XBIG {
return 0.0;
}
if ax < LARGE {
erfc_mid(ax)
} else {
erfc_tail(ax)
}
}
fn erfc_mid(ax: f64) -> f64 {
exp_weight(ax) * cody_ratio(ax, &C_ERFC, &D_ERFC)
}
fn erfc_tail(ax: f64) -> f64 {
let z = 1.0 / (ax * ax);
let r = z * cody_ratio(z, &P_ASYMP, &Q_ASYMP);
let scaled = (INV_SQRT_PI - r) / ax;
exp_weight(ax) * scaled
}
fn exp_weight(ax: f64) -> f64 {
let t = trunc_16(ax);
let d = ax - t;
(-t * t).exp() * (-d * (ax + t)).exp()
}
fn trunc_16(ax: f64) -> f64 {
(ax * 16.0).floor() / 16.0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn erf_one_high_precision() {
assert!(
(erf(1.0) - 0.842_700_792_949_714_9).abs() < 1e-15,
"erf(1) was {}",
erf(1.0)
);
}
#[test]
fn erf_is_odd() {
for &x in &[0.3, 0.7, 1.5, 3.0, 6.0] {
assert!((erf(-x) + erf(x)).abs() < 1e-15, "erf not odd at {x}");
}
}
#[test]
fn erf_half_boundary() {
assert!(
(erf(0.5) - 0.520_499_877_813_046_5).abs() < 1e-15,
"erf(0.5) was {}",
erf(0.5)
);
}
#[test]
fn erf_two() {
assert!(
(erf(2.0) - 0.995_322_265_018_952_7).abs() < 1e-15,
"erf(2) was {}",
erf(2.0)
);
}
#[test]
fn erfc_deep_tail() {
let got = erfc(5.0);
let want = 1.537_459_794_428_035e-12;
assert!(
((got - want) / want).abs() < 1e-12,
"erfc(5) rel error too large: got {got}, want {want}"
);
}
#[test]
fn erfc_large_boundary() {
let got = erfc(4.0);
let want = 1.541_725_790_028_002e-8;
assert!(
((got - want) / want).abs() < 1e-12,
"erfc(4) rel error too large: got {got}, want {want}"
);
}
#[test]
fn erfc_complements_erf() {
for &x in &[-0.3, 0.0, 0.4, 1.0] {
assert!(
(erfc(x) - (1.0 - erf(x))).abs() < 1e-15,
"erfc != 1-erf at {x}"
);
}
}
#[test]
fn infinities() {
assert!((erf(f64::INFINITY) - 1.0).abs() < 1e-15);
assert!((erf(f64::NEG_INFINITY) + 1.0).abs() < 1e-15);
assert!(erfc(f64::INFINITY).abs() < 1e-300);
assert!((erfc(f64::NEG_INFINITY) - 2.0).abs() < 1e-15);
}
#[test]
fn ln_erfc_matches_log_of_erfc_in_body() {
for &x in &[-1.0, 0.0, 0.5, 1.0, 2.0, 4.0] {
let want = erfc(x).ln();
let got = ln_erfc(x);
assert!(
((got - want) / want.abs().max(1.0)).abs() < 1e-12,
"ln_erfc({x}) = {got}, want {want}"
);
}
}
#[test]
fn ln_erfc_finite_in_deep_tail() {
assert_eq!(erfc(40.0).to_bits(), 0.0_f64.to_bits());
let got = ln_erfc(40.0);
let want = -1_604.261_556_653_274_3;
assert!(got.is_finite(), "ln_erfc(40) was {got}");
assert!(
((got - want) / want).abs() < 1e-9,
"ln_erfc(40) rel error too large: got {got}, want {want}"
);
}
}