pub(crate) const LOG2E: f32 = std::f32::consts::LOG2_E;
pub(crate) const LN2_HI: f32 = std::f32::consts::LN_2;
pub(crate) const LN2_LO: f32 = -1.904_654_3e-9;
pub(crate) const MAGIC: f32 = 12_582_912.0;
pub(crate) const X_MAX: f32 = 89.0;
pub(crate) const X_MIN: f32 = -104.0;
pub(crate) const Q: [f32; 6] = [
0.5,
0.166_666_67,
0.041_666_463,
0.008_333_298,
0.001_393_368,
0.000_198_993_18,
];
#[inline(always)]
fn pow2(n: i32) -> f32 {
f32::from_bits(((n + 127) as u32) << 23)
}
#[inline(always)]
pub(crate) fn exp(x: f32) -> f32 {
let x = x.clamp(X_MIN, X_MAX);
let n_f = x.mul_add(LOG2E, MAGIC) - MAGIC;
let n = n_f as i32;
let r = (-n_f).mul_add(LN2_HI, x);
let r = (-n_f).mul_add(LN2_LO, r);
let q = Q[5]
.mul_add(r, Q[4])
.mul_add(r, Q[3])
.mul_add(r, Q[2])
.mul_add(r, Q[1])
.mul_add(r, Q[0]);
let tail = (r * r) * q;
let hi = 1.0 + r;
let lo = r - (hi - 1.0);
let e = hi + (lo + tail);
let half = n >> 1;
e * pow2(half) * pow2(n - half)
}
#[inline(always)]
pub fn sigmoid(x: f32) -> f32 {
1.0 / (1.0 + exp(-x))
}
#[cfg(test)]
mod tests {
use super::*;
fn ulps(a: f32, b: f32) -> u32 {
let key = |x: f32| {
let bits = x.to_bits() as i64;
if bits & 0x8000_0000 != 0 {
-(bits & 0x7fff_ffff)
} else {
bits
}
};
(key(a) - key(b)).unsigned_abs() as u32
}
fn nearly_exact(x: f32) -> f32 {
f64::from(x).exp() as f32
}
#[test]
fn exp_of_the_special_values() {
assert_eq!(exp(0.0), 1.0);
assert_eq!(exp(-0.0), 1.0);
assert_eq!(exp(f32::INFINITY), f32::INFINITY);
assert_eq!(exp(f32::NEG_INFINITY).to_bits(), 0);
assert!(exp(f32::NAN).is_nan());
assert!(exp(-f32::NAN).is_nan());
}
#[test]
fn exp_of_one_is_e() {
assert_eq!(exp(1.0), std::f32::consts::E);
assert_eq!(exp(-1.0), 1.0 / std::f32::consts::E);
}
#[test]
fn exp_overflows_exactly_where_the_result_leaves_f32() {
let threshold = f64::from(f32::MAX) * (1.0 + 2f64.powi(-24));
let edge = 88.722_84_f32.to_bits();
for bits in edge - 40..edge + 40 {
let x = f32::from_bits(bits);
let finite = f64::from(x).exp() < threshold;
assert_eq!(exp(x).is_finite(), finite, "x={x}");
if finite {
assert!(ulps(exp(x), nearly_exact(x)) <= 1, "x={x}");
}
}
assert_eq!(exp(100.0), f32::INFINITY);
assert_eq!(exp(1.0e30), f32::INFINITY);
}
#[test]
fn exp_underflows_through_the_subnormals_to_zero() {
let smallest = f32::from_bits(1);
assert_eq!(exp(-103.0), nearly_exact(-103.0));
assert_eq!(exp(-103.27), smallest);
assert_eq!(exp(-103.99).to_bits(), 0);
assert_eq!(exp(-104.0).to_bits(), 0);
assert_eq!(exp(-1.0e30).to_bits(), 0);
for k in 1..=40 {
let x = -87.0 - 0.4 * k as f32;
assert!(ulps(exp(x), nearly_exact(x)) <= 1, "x={x}");
}
}
#[test]
fn exp_is_within_one_ulp_of_the_exact_result() {
let mut state = 7u64;
for _ in 0..200_000 {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let x = f32::from_bits((state >> 32) as u32);
if !x.is_finite() || !(-103.0..=88.0).contains(&x) {
continue;
}
assert!(ulps(exp(x), nearly_exact(x)) <= 1, "x={x}");
}
}
#[test]
fn exp_is_exact_for_tiny_arguments() {
for e in 26..60 {
let x = 2f32.powi(-e);
assert_eq!(exp(x), 1.0, "x=2^-{e}");
assert_eq!(exp(-x), 1.0, "x=-2^-{e}");
}
}
#[test]
fn sigmoid_of_the_special_values_and_the_edges() {
assert_eq!(sigmoid(0.0), 0.5);
assert_eq!(sigmoid(f32::INFINITY), 1.0);
assert_eq!(sigmoid(f32::NEG_INFINITY), 0.0);
assert!(sigmoid(f32::NAN).is_nan());
assert_eq!(sigmoid(20.0), 1.0);
assert_eq!(sigmoid(-88.8).to_bits(), 0);
assert_eq!(sigmoid(-100.0).to_bits(), 0);
assert_eq!(sigmoid(-1.0e30).to_bits(), 0);
}
#[test]
fn sigmoid_matches_the_f64_formula_within_two_ulp() {
for i in -2000..=2000 {
let x = i as f32 * 0.01;
let want = (1.0 / (1.0 + (-f64::from(x)).exp())) as f32;
assert!(ulps(sigmoid(x), want) <= 2, "x={x}");
}
}
}