use super::{modulo, PI};
const LA: [f64; 12] = [
7.72156649015328655494e-02, 3.22467033424113591611e-01, 6.73523010531292681824e-02, 2.05808084325167332806e-02, 7.38555086081402883957e-03, 2.89051383673415629091e-03, 1.19270763183362067845e-03, 5.10069792153511336608e-04, 2.20862790713908385557e-04, 1.08011567247583939954e-04, 2.52144565451257326939e-05, 4.48640949618915160150e-05, ];
const LR: [f64; 7] = [
1.0, 1.39200533467621045958e+00, 7.21935547567138069525e-01, 1.71933865632803078993e-01, 1.86459191715652901344e-02, 7.77942496381893596434e-04, 7.32668430744625636189e-06, ];
const LS: [f64; 7] = [
-7.72156649015328655494e-02, 2.14982415960608852501e-01, 3.25778796408930981787e-01, 1.46350472652464452805e-01, 2.66422703033638609560e-02, 1.84028451407337715652e-03, 3.19475326584100867617e-05, ];
const LT: [f64; 15] = [
4.83836122723810047042e-01, -1.47587722994593911752e-01, 6.46249402391333854778e-02, -3.27885410759859649565e-02, 1.79706750811820387126e-02, -1.03142241298341437450e-02, 6.10053870246291332635e-03, -3.68452016781138256760e-03, 2.25964780900612472250e-03, -1.40346469989232843813e-03, 8.81081882437654011382e-04, -5.38595305356740546715e-04, 3.15632070903625950361e-04, -3.12754168375120860518e-04, 3.35529192635519073543e-04, ];
const LU: [f64; 6] = [
-7.72156649015328655494e-02, 6.32827064025093366517e-01, 1.45492250137234768737e+00, 9.77717527963372745603e-01, 2.28963728064692451092e-01, 1.33810918536787660377e-02, ];
const LV: [f64; 6] = [
1.0,
2.45597793713041134822e+00, 2.12848976379893395361e+00, 7.69285150456672783825e-01, 1.04222645593369134254e-01, 3.21709242282423911810e-03, ];
const LW: [f64; 7] = [
4.18938533204672725052e-01, 8.33333333333329678849e-02, -2.77777777728775536470e-03, 7.93650558643019558500e-04, -5.95187557450339963135e-04, 8.36339918996282139126e-04, -1.63092934096575273989e-03, ];
const Y_MIN: f64 = 1.461632144968362245;
const TWO_52: f64 = 4.5035996273704960000000000000000000000000000000000e15;
const TWO_53: f64 = 9.0071992547409920000000000000000000000000000000000e15;
const TWO_58: f64 = 2.8823037615171174400000000000000000000000000000000e17;
const TINY: f64 = 8.470329472543003390683225006796419620513916015625000000000000000e-22;
const TC: f64 = 1.46163214496836224576e+00;
const TF: f64 = -1.21486290535849611461e-01;
const TT: f64 = -3.63867699703950536541e-18;
pub fn ln_gamma(x: f64) -> (f64, i32) {
if f64::is_nan(x) {
return (x, 1);
} else if f64::is_infinite(x) {
return (x, 1);
} else if x == 0.0 {
return (f64::INFINITY, 1);
}
let mut negative = false;
let mut xx = x;
if xx < 0.0 {
xx = -xx;
negative = true;
}
let mut sign = 1;
if xx < TINY {
if negative {
sign = -1;
}
return (-f64::ln(xx), sign);
}
let mut n_adj: f64 = 0.0;
if negative {
if xx >= TWO_52 {
return (f64::INFINITY, sign);
}
let t = sin_pi_times_neg_x_given_abs_x(xx);
if t == 0.0 {
return (f64::INFINITY, sign); }
n_adj = f64::ln(PI / f64::abs(t * xx));
if t < 0.0 {
sign = -1;
}
}
if xx == 1.0 || xx == 2.0 {
return (0.0, sign);
}
let mut lgamma: f64;
if xx < 2.0 {
let (y, i) = if xx <= 0.9 {
lgamma = -f64::ln(xx);
if xx >= Y_MIN - 1.0 + 0.27 {
(1.0 - xx, 0)
} else if xx >= Y_MIN - 1.0 - 0.27 {
(xx - (TC - 1.0), 1)
} else {
(xx, 2)
}
} else {
lgamma = 0.0;
if xx >= Y_MIN + 0.27 {
(2.0 - xx, 0)
} else if xx >= Y_MIN - 0.27 {
(xx - TC, 1)
} else {
(xx - 1.0, 2)
}
};
if i == 0 {
let z = y * y;
let p1 = LA[0] + z * (LA[2] + z * (LA[4] + z * (LA[6] + z * (LA[8] + z * LA[10]))));
let p2 = z * (LA[1] + z * (LA[3] + z * (LA[5] + z * (LA[7] + z * (LA[9] + z * LA[11])))));
let p = y * p1 + p2;
lgamma += p - 0.5 * y;
} else if i == 1 {
let z = y * y;
let w = z * y;
let p1 = LT[0] + w * (LT[3] + w * (LT[6] + w * (LT[9] + w * LT[12]))); let p2 = LT[1] + w * (LT[4] + w * (LT[7] + w * (LT[10] + w * LT[13])));
let p3 = LT[2] + w * (LT[5] + w * (LT[8] + w * (LT[11] + w * LT[14])));
let p = z * p1 - (TT - w * (p2 + y * p3));
lgamma += TF + p;
} else {
let p1 = y * (LU[0] + y * (LU[1] + y * (LU[2] + y * (LU[3] + y * (LU[4] + y * LU[5])))));
let p2 = 1.0 + y * (LV[1] + y * (LV[2] + y * (LV[3] + y * (LV[4] + y * LV[5]))));
lgamma += -0.5 * y + p1 / p2;
}
} else if xx < 8.0 {
let i = xx as i32;
let y = xx - (i as f64);
let p = y * (LS[0] + y * (LS[1] + y * (LS[2] + y * (LS[3] + y * (LS[4] + y * (LS[5] + y * LS[6]))))));
let q = 1.0 + y * (LR[1] + y * (LR[2] + y * (LR[3] + y * (LR[4] + y * (LR[5] + y * LR[6])))));
lgamma = 0.5 * y + p / q;
let mut z = 1.0; if i >= 7 {
z *= y + 6.0;
}
if i >= 6 {
z *= y + 5.0;
}
if i >= 5 {
z *= y + 4.0;
}
if i >= 4 {
z *= y + 3.0;
}
if i >= 3 {
z *= y + 2.0;
lgamma += f64::ln(z);
}
} else if xx < TWO_58 {
let t = f64::ln(xx);
let z = 1.0 / xx;
let y = z * z;
let w = LW[0] + z * (LW[1] + y * (LW[2] + y * (LW[3] + y * (LW[4] + y * (LW[5] + y * LW[6])))));
lgamma = (xx - 0.5) * (t - 1.0) + w;
} else {
lgamma = xx * (f64::ln(xx) - 1.0);
}
if negative {
lgamma = n_adj - lgamma;
}
(lgamma, sign)
}
fn sin_pi_times_neg_x_given_abs_x(abs_x: f64) -> f64 {
assert!(abs_x >= 0.0);
if abs_x < 0.25 {
return -f64::sin(PI * abs_x);
}
let mut xx = abs_x;
let mut z = f64::floor(xx);
let mut n: i32;
if z != xx {
xx = modulo(xx, 2.0);
n = (xx * 4.0) as i32;
} else {
if xx >= TWO_53 {
xx = 0.0;
n = 0;
} else {
if xx < TWO_52 {
z = xx + TWO_52; }
n = (1 & f64::to_bits(z)) as i32;
xx = n as f64;
n <<= 2;
}
}
if n == 0 {
-f64::sin(PI * xx)
} else if n == 1 || n == 2 {
-f64::cos(PI * (0.5 - xx))
} else if n == 3 || n == 4 {
-f64::sin(PI * (1.0 - xx))
} else if n == 5 || n == 6 {
f64::cos(PI * (xx - 1.5))
} else {
-f64::sin(PI * (xx - 2.0))
}
}
#[cfg(test)]
mod tests {
use super::{ln_gamma, sin_pi_times_neg_x_given_abs_x, TINY, TWO_52, TWO_53, TWO_58, Y_MIN};
use crate::math::{ONE_BY_3, PI};
use crate::{approx_eq, assert_alike};
#[test]
fn sin_pi_times_neg_x_given_abs_x_works() {
approx_eq(sin_pi_times_neg_x_given_abs_x(0.15), f64::sin(-PI * 0.15), 1e-50);
approx_eq(sin_pi_times_neg_x_given_abs_x(2.0), f64::sin(-PI * 2.0), 1e-15);
approx_eq(sin_pi_times_neg_x_given_abs_x(0.25), f64::sin(-PI * 0.25), 1e-15);
approx_eq(sin_pi_times_neg_x_given_abs_x(0.5), f64::sin(-PI * 0.5), 1e-15);
approx_eq(sin_pi_times_neg_x_given_abs_x(0.75), f64::sin(-PI * 0.75), 1e-15);
approx_eq(sin_pi_times_neg_x_given_abs_x(1.0), f64::sin(-PI * 1.0), 1e-15);
approx_eq(sin_pi_times_neg_x_given_abs_x(1.25), f64::sin(-PI * 1.25), 1e-15);
approx_eq(sin_pi_times_neg_x_given_abs_x(1.5), f64::sin(-PI * 1.5), 1e-15);
approx_eq(sin_pi_times_neg_x_given_abs_x(1.75), f64::sin(-PI * 1.75), 1e-15);
approx_eq(sin_pi_times_neg_x_given_abs_x(1.9), f64::sin(-PI * 1.9), 1e-15);
let res = sin_pi_times_neg_x_given_abs_x(TWO_53);
assert_eq!(res, 0.0);
assert_eq!(res.is_sign_negative(), true);
let res = sin_pi_times_neg_x_given_abs_x(TWO_52);
assert_eq!(res, 0.0);
assert_eq!(res.is_sign_negative(), true);
}
#[test]
fn ln_gamma_works() {
assert!(ln_gamma(f64::NAN).0.is_nan());
assert_eq!(ln_gamma(-1.0).0, f64::INFINITY);
assert_eq!(ln_gamma(0.0).0, f64::INFINITY);
assert_eq!(ln_gamma(1.0).0, 0.0);
assert_eq!(ln_gamma(2.0).0, 0.0);
let mathematica = [
(0.1, 1e-15, 2.252712651734206),
(ONE_BY_3, 1e-15, 0.98542064692776706918717403697796139173555649638589),
(0.5, 1e-50, 0.5723649429247001),
(3.0, 1e-50, 0.69314718055994530941723212145817656807550013436026),
(10.0, 1e-14, 12.801827480081469611207717874566706164281149255663),
(33.0, 1e-13, 81.557959456115037178502968666011206687099284403417),
];
for (x, tol, reference) in mathematica {
approx_eq(ln_gamma(x).0, reference, tol)
}
}
#[test]
fn ln_gamma_branches_work() {
let x = TINY / 2.0;
let (y, s) = ln_gamma(x);
approx_eq(y, 49.213449819756116968623236163187614885368991246517, 1e-50); assert_eq!(s, 1);
let (y, s) = ln_gamma(-x);
approx_eq(y, 49.213449819756116968623725083873457781352028127685, 1e-50); assert_eq!(s, -1);
let x = -TWO_52;
assert_eq!(ln_gamma(x), (f64::INFINITY, 1));
let x = Y_MIN - 1.0 + 0.27;
approx_eq(ln_gamma(x).0, 0.2236602461341474, 1e-16);
let x = Y_MIN + 0.27;
approx_eq(ln_gamma(x).0, -0.0888171793955331, 1e-16);
let x = Y_MIN - 0.27;
approx_eq(ln_gamma(x).0, -0.0829109295390456, 1e-14);
let x = Y_MIN - 0.27 - 0.01;
approx_eq(ln_gamma(x).0, -0.07984971231516993, 1e-50);
approx_eq(ln_gamma(2.1).0, 0.04543773854448518, 1e-50);
approx_eq(
ln_gamma(8.0).0,
8.5251613610654143001655310363471250507596677369369,
1e-14,
);
approx_eq(
ln_gamma(TWO_58).0,
1.1299361833563194928501116868607855707791504631856e19,
1e-50,
);
let (y, s) = ln_gamma(-0.24);
approx_eq(y, 1.619664916633736, 1e-15); assert_eq!(s, -1);
let (y, s) = ln_gamma(-0.25);
approx_eq(y, 1.589575312551186, 1e-50); assert_eq!(s, -1);
let (y, s) = ln_gamma(-1.5);
approx_eq(y, 0.860047015376481, 1e-15); assert_eq!(s, 1);
assert_eq!(ln_gamma(-2.0).0, f64::INFINITY);
approx_eq(ln_gamma(-2.5).0, -0.05624371649767435, 1e-15); }
struct Fi {
f: f64,
i: i32,
}
const VALUES: [f64; 10] = [
4.9790119248836735e+00,
7.7388724745781045e+00,
-2.7688005719200159e-01,
-5.0106036182710749e+00,
9.6362937071984173e+00,
2.9263772392439646e+00,
5.2290834314593066e+00,
2.7279399104360102e+00,
1.8253080916808550e+00,
-8.6859247685756013e+00,
];
#[rustfmt::skip]
const SOLUTION: [Fi; 10] = [
Fi { f: 3.146492141244545774319734e+00, i: 1 },
Fi { f: 8.003414490659126375852113e+00, i: 1 },
Fi { f: 1.517575735509779707488106e+00, i: -1 },
Fi { f: -2.588480028182145853558748e-01, i: 1 },
Fi { f: 1.1989897050205555002007985e+01, i: 1 },
Fi { f: 6.262899811091257519386906e-01, i: 1 },
Fi { f: 3.5287924899091566764846037e+00, i: 1 },
Fi { f: 4.5725644770161182299423372e-01, i: 1 },
Fi { f: -6.363667087767961257654854e-02, i: 1 },
Fi { f: -1.077385130910300066425564e+01, i: -1 },
];
const SC_VALUES: [f64; 7] = [f64::NEG_INFINITY, -3.0, 0.0, 1.0, 2.0, f64::INFINITY, f64::NAN];
#[rustfmt::skip]
const SC_SOLUTION: [Fi; 7] = [
Fi { f: f64::NEG_INFINITY, i: 1 },
Fi { f: f64::INFINITY, i: 1 },
Fi { f: f64::INFINITY, i: 1 },
Fi { f: 0.0, i: 1 },
Fi { f: 0.0, i: 1 },
Fi { f: f64::INFINITY, i: 1 },
Fi { f: f64::NAN, i: 1 },
];
#[test]
fn test_ln_gamma() {
for (i, v) in VALUES.iter().enumerate() {
let (f, s) = ln_gamma(*v);
approx_eq(SOLUTION[i].f, f, 1e-14);
assert_eq!(SOLUTION[i].i, s);
}
for (i, v) in SC_VALUES.iter().enumerate() {
let (f, s) = ln_gamma(*v);
assert_alike(SC_SOLUTION[i].f, f);
assert_eq!(SC_SOLUTION[i].i, s);
}
}
}