#![allow(clippy::excessive_precision)]
use crate::types::Real;
const TINY: Real = Real::EPSILON;
const ONE: Real = 1.0;
const ERX: Real = 8.45062911510467529297e-01;
const EFX: Real = 1.28379167095512586316e-01;
const EFX8: Real = 1.02703333676410069053e+00;
const PP0: Real = 1.28379167095512558561e-01;
const PP1: Real = -3.25042107247001499370e-01;
const PP2: Real = -2.84817495755985104766e-02;
const PP3: Real = -5.77027029648944159157e-03;
const PP4: Real = -2.37630166566501626084e-05;
const QQ1: Real = 3.97917223959155352819e-01;
const QQ2: Real = 6.50222499887672944485e-02;
const QQ3: Real = 5.08130628187576562776e-03;
const QQ4: Real = 1.32494738004321644526e-04;
const QQ5: Real = -3.96022827877536812320e-06;
const PA0: Real = -2.36211856075265944077e-03;
const PA1: Real = 4.14856118683748331666e-01;
const PA2: Real = -3.72207876035701323847e-01;
const PA3: Real = 3.18346619901161753674e-01;
const PA4: Real = -1.10894694282396677476e-01;
const PA5: Real = 3.54783043256182359371e-02;
const PA6: Real = -2.16637559486879084300e-03;
const QA1: Real = 1.06420880400844228286e-01;
const QA2: Real = 5.40397917702171048937e-01;
const QA3: Real = 7.18286544141962662868e-02;
const QA4: Real = 1.26171219808761642112e-01;
const QA5: Real = 1.36370839120290507362e-02;
const QA6: Real = 1.19844998467991074170e-02;
const RA0: Real = -9.86494403484714822705e-03;
const RA1: Real = -6.93858572707181764372e-01;
const RA2: Real = -1.05586262253232909814e+01;
const RA3: Real = -6.23753324503260060396e+01;
const RA4: Real = -1.62396669462573470355e+02;
const RA5: Real = -1.84605092906711035994e+02;
const RA6: Real = -8.12874355063065934246e+01;
const RA7: Real = -9.81432934416914548592e+00;
const SA1: Real = 1.96512716674392571292e+01;
const SA2: Real = 1.37657754143519042600e+02;
const SA3: Real = 4.34565877475229228821e+02;
const SA4: Real = 6.45387271733267880336e+02;
const SA5: Real = 4.29008140027567833386e+02;
const SA6: Real = 1.08635005541779435134e+02;
const SA7: Real = 6.57024977031928170135e+00;
const SA8: Real = -6.04244152148580987438e-02;
const RB0: Real = -9.86494292470009928597e-03;
const RB1: Real = -7.99283237680523006574e-01;
const RB2: Real = -1.77579549177547519889e+01;
const RB3: Real = -1.60636384855821916062e+02;
const RB4: Real = -6.37566443368389627722e+02;
const RB5: Real = -1.02509513161107724954e+03;
const RB6: Real = -4.83519191608651397019e+02;
const SB1: Real = 3.03380607434824582924e+01;
const SB2: Real = 3.25792512996573918826e+02;
const SB3: Real = 1.53672958608443695994e+03;
const SB4: Real = 3.19985821950859553908e+03;
const SB5: Real = 2.55305040643316442583e+03;
const SB6: Real = 4.74528541206955367215e+02;
const SB7: Real = -2.24409524465858183362e+01;
pub fn erf(x: Real) -> Real {
if !x.is_finite() {
return if x.is_nan() {
x
} else if x > 0.0 {
1.0
} else {
-1.0
};
}
let ax = x.abs();
if ax < 0.84375 {
if ax < 3.7252902984e-09 {
if ax < Real::MIN_POSITIVE * 16.0 {
return 0.125 * (8.0 * x + EFX8 * x); }
return x + EFX * x;
}
let z = x * x;
let r = PP0 + z * (PP1 + z * (PP2 + z * (PP3 + z * PP4)));
let s = ONE + z * (QQ1 + z * (QQ2 + z * (QQ3 + z * (QQ4 + z * QQ5))));
return x + x * (r / s);
}
if ax < 1.25 {
let s = ax - ONE;
let p = PA0 + s * (PA1 + s * (PA2 + s * (PA3 + s * (PA4 + s * (PA5 + s * PA6)))));
let q = ONE + s * (QA1 + s * (QA2 + s * (QA3 + s * (QA4 + s * (QA5 + s * QA6)))));
return if x >= 0.0 { ERX + p / q } else { -ERX - p / q };
}
if ax >= 6.0 {
return if x >= 0.0 { ONE - TINY } else { TINY - ONE };
}
let s = ONE / (ax * ax);
let (r, denom) = if ax < 2.85714285714285 {
let r =
RA0 + s * (RA1 + s * (RA2 + s * (RA3 + s * (RA4 + s * (RA5 + s * (RA6 + s * RA7))))));
let denom = ONE
+ s * (SA1
+ s * (SA2 + s * (SA3 + s * (SA4 + s * (SA5 + s * (SA6 + s * (SA7 + s * SA8)))))));
(r, denom)
} else {
let r = RB0 + s * (RB1 + s * (RB2 + s * (RB3 + s * (RB4 + s * (RB5 + s * RB6)))));
let denom =
ONE + s * (SB1 + s * (SB2 + s * (SB3 + s * (SB4 + s * (SB5 + s * (SB6 + s * SB7))))));
(r, denom)
};
let r = (-ax * ax - 0.5625 + r / denom).exp();
if x >= 0.0 { ONE - r / ax } else { r / ax - ONE }
}
#[cfg(test)]
mod tests {
use super::*;
const TOL: Real = 1e-15;
fn close(a: Real, b: Real) -> bool {
(a - b).abs() <= TOL
}
#[test]
fn known_values_across_regions() {
assert_eq!(erf(0.0), 0.0);
assert!(close(erf(0.1), 0.112_462_916_018_284_89));
assert!(close(erf(0.5), 0.520_499_877_813_046_5));
assert!(close(erf(0.9), 0.796_908_212_422_832_1));
assert!(close(erf(1.0), 0.842_700_792_949_714_9));
assert!(close(erf(1.5), 0.966_105_146_475_310_7));
assert!(close(erf(2.0), 0.995_322_265_018_952_7));
assert!(close(erf(4.0), 0.999_999_984_582_742_1));
}
#[test]
fn tiny_argument_uses_linear_branch() {
let x = 1e-10;
assert!(close(erf(x), x * (1.0 + EFX)));
}
#[test]
fn odd_symmetry() {
for &x in &[0.3, 0.95, 1.7, 3.0, 5.0] {
assert!(close(erf(-x), -erf(x)));
}
}
#[test]
fn saturates_and_handles_nonfinite() {
assert!(close(erf(6.0), 1.0));
assert!(close(erf(-6.0), -1.0));
assert_eq!(erf(Real::INFINITY), 1.0);
assert_eq!(erf(Real::NEG_INFINITY), -1.0);
assert!(erf(Real::NAN).is_nan());
}
}