#[inline(always)]
pub fn fast_ln(x: f32) -> f32 {
let i = x.to_bits();
let m = f32::from_bits((i & 0x007F_FFFF) | 0x3f00_0000);
let y = i as i32 as f32 * 1.192_092_9e-7;
std::f32::consts::LN_2 * (y - 124.225_52 - 1.498_030_3 * m - 1.725_88 / (0.352_088_7 + m))
}
#[inline(always)]
fn fast_ln_gamma(x: f32) -> f32 {
let xp3 = 3.0 + x;
-2.081_061_5 - x + 0.083_333_3 / xp3 - fast_ln(x * (1.0 + x) * (2.0 + x))
+ (2.5 + x) * fast_ln(xp3)
}
const MIDPOINT_FROM: f32 = 8.0;
#[inline(always)]
pub fn ln_gamma_increment(x: f32, y: f32) -> f32 {
debug_assert!(x > 0.0, "{x}");
let inv = 1.0 / x;
debug_assert!(y >= 0.0, "{y}");
if inv > expansion_limit(y) {
short_of_expansion(x, y)
} else {
ExpansionCoefficients::of(y).expansion(fast_ln(x), inv)
}
}
#[inline(always)]
pub fn ln_gamma_increments(run: LnRun<'_>, y: f32, out: &mut [f32]) {
assert_eq!(run.xs.len(), out.len());
debug_assert!(y >= 0.0, "{y}");
let coefficients = ExpansionCoefficients::of(y);
let limit = expansion_limit(y);
let mut any_fails = false;
for ((inc, &ln_x), &inv) in out.iter_mut().zip(run.ln_xs).zip(run.inv_xs) {
*inc = coefficients.expansion(ln_x, inv);
any_fails |= inv > limit;
}
if any_fails {
for ((inc, &x), &inv) in out.iter_mut().zip(run.xs).zip(run.inv_xs) {
if inv > limit {
*inc = short_of_expansion(x, y);
}
}
}
}
const EXPANSION_FROM: f32 = 8.0;
const EXPANSION_UP_TO: f32 = 1e9;
#[inline(always)]
fn expansion_limit(y: f32) -> f32 {
if y > EXPANSION_UP_TO {
-1.0
} else {
1.0 / (EXPANSION_FROM * y.max(1.0))
}
}
struct ExpansionCoefficients {
y: f32,
c2: f32,
c3: f32,
c4: f32,
}
impl ExpansionCoefficients {
#[inline(always)]
fn of(y: f32) -> Self {
let c2 = 0.5 * y * (y - 1.0);
ExpansionCoefficients {
y,
c2,
c3: c2 * (2.0 * y - 1.0) * (1.0 / 6.0),
c4: c2 * c2 * (1.0 / 3.0),
}
}
#[inline(always)]
fn expansion(&self, ln_x: f32, inv: f32) -> f32 {
self.y * ln_x + inv * (self.c2 - inv * (self.c3 - inv * self.c4))
}
}
#[inline(always)]
fn short_of_expansion(x: f32, y: f32) -> f32 {
if y < x && x >= MIDPOINT_FROM {
let mid = x + 0.5 * (y - 1.0);
let inv_mid = 1.0 / mid;
let ratio = y * inv_mid;
let squared = ratio * ratio;
let slope = fast_ln(mid) + inv_mid * inv_mid * (1.0 / 24.0);
y * (slope - squared * (1.0 / 24.0 + squared * (1.0 / 320.0)))
} else {
fast_ln_gamma(x + y) - fast_ln_gamma(x)
}
}
#[derive(Clone)]
pub struct LnTable {
xs: Vec<f32>,
ln_xs: Vec<f32>,
inv_xs: Vec<f32>,
}
#[derive(Copy, Clone)]
pub struct LnRun<'a> {
xs: &'a [f32],
ln_xs: &'a [f32],
inv_xs: &'a [f32],
}
impl LnTable {
pub fn new(n: usize) -> Self {
LnTable {
xs: vec![f32::NAN; n],
ln_xs: vec![f32::NAN; n],
inv_xs: vec![f32::INFINITY; n],
}
}
#[inline]
pub fn set(&mut self, i: usize, x: f32) {
debug_assert!(x > 0.0, "{x}");
self.xs[i] = x;
self.ln_xs[i] = fast_ln(x);
self.inv_xs[i] = 1.0 / x;
}
#[inline]
pub fn run(&self, range: std::ops::Range<usize>) -> LnRun<'_> {
LnRun {
xs: &self.xs[range.clone()],
ln_xs: &self.ln_xs[range.clone()],
inv_xs: &self.inv_xs[range],
}
}
pub fn cell(&self, i: usize) -> [f32; 3] {
[self.xs[i], self.ln_xs[i], self.inv_xs[i]]
}
}
#[cfg(test)]
#[path = "fast_math_tests.rs"]
mod tests;