#![allow(clippy::excessive_precision)]
#![allow(clippy::approx_constant)]
use crate::algo::constants::R1M5;
use crate::machine::BesselFloat;
use crate::types::Error;
const CON: f64 = 1.83787706640934548;
#[rustfmt::skip]
const GLN_TABLE: [f64; 100] = [
0.00000000000000000e+00, 0.00000000000000000e+00, 6.93147180559945309e-01, 1.79175946922805500e+00, 3.17805383034794562e+00, 4.78749174278204599e+00,
6.57925121201010100e+00, 8.52516136106541430e+00,
1.06046029027452502e+01, 1.28018274800814696e+01,
1.51044125730755153e+01, 1.75023078458738858e+01,
1.99872144956618861e+01, 2.25521638531234229e+01,
2.51912211827386815e+01, 2.78992713838408916e+01,
3.06718601060806728e+01, 3.35050734501368889e+01,
3.63954452080330536e+01, 3.93398841871994940e+01,
4.23356164607534850e+01, 4.53801388984769080e+01,
4.84711813518352239e+01, 5.16066755677643736e+01,
5.47847293981123192e+01, 5.80036052229805199e+01,
6.12617017610020020e+01, 6.45575386270063311e+01,
6.78897431371815350e+01, 7.12570389671680090e+01,
7.46582363488301644e+01, 7.80922235533153106e+01,
8.15579594561150372e+01, 8.50544670175815174e+01,
8.85808275421976788e+01, 9.21361756036870925e+01,
9.57196945421432025e+01, 9.93306124547874269e+01,
1.02968198614513813e+02, 1.06631760260643459e+02,
1.10320639714757395e+02, 1.14034211781461703e+02,
1.17771881399745072e+02, 1.21533081515438634e+02,
1.25317271149356895e+02, 1.29123933639127215e+02,
1.32952575035616310e+02, 1.36802722637326368e+02,
1.40673923648234259e+02, 1.44565743946344886e+02,
1.48477766951773032e+02, 1.52409592584497358e+02,
1.56360836303078785e+02, 1.60331128216630907e+02,
1.64320112263195181e+02, 1.68327445448427652e+02,
1.72352797139162802e+02, 1.76395848406997352e+02,
1.80456291417543771e+02, 1.84533828861449491e+02,
1.88628173423671591e+02, 1.92739047287844902e+02,
1.96866181672889994e+02, 2.01009316399281527e+02,
2.05168199482641199e+02, 2.09342586752536836e+02,
2.13532241494563261e+02, 2.17736934113954227e+02,
2.21956441819130334e+02, 2.26190548323727593e+02,
2.30439043565776952e+02, 2.34701723442818268e+02,
2.38978389561834323e+02, 2.43268849002982714e+02,
2.47572914096186884e+02, 2.51890402209723194e+02,
2.56221135550009525e+02, 2.60564940971863209e+02,
2.64921649798552801e+02, 2.69291097651019823e+02,
2.73673124285693704e+02, 2.78067573440366143e+02,
2.82474292687630396e+02, 2.86893133295426994e+02,
2.91323950094270308e+02, 2.95766601350760624e+02,
3.00220948647014132e+02, 3.04686856765668715e+02,
3.09164193580146922e+02, 3.13652829949879062e+02,
3.18152639620209327e+02, 3.22663499126726177e+02,
3.27185287703775217e+02, 3.31717887196928473e+02,
3.36261181979198477e+02, 3.40815058870799018e+02,
3.45379407062266854e+02, 3.49954118040770237e+02,
3.54539085519440809e+02, 3.59134205369575399e+02,
];
#[rustfmt::skip]
const CF_TABLE: [f64; 22] = [
8.33333333333333333e-02, -2.77777777777777778e-03, 7.93650793650793651e-04, -5.95238095238095238e-04, 8.41750841750841751e-04, -1.91752691752691753e-03, 6.41025641025641026e-03, -2.95506535947712418e-02, 1.79644372368830573e-01, -1.39243221690590112e+00, 1.34028640441683920e+01, -1.56848284626002017e+02,
2.19310333333333333e+03,
-3.61087712537249894e+04,
6.91472268851313067e+05,
-1.52382215394074162e+07,
3.82900751391414141e+08,
-1.08822660357843911e+10,
3.47320283765002252e+11,
-1.23696021422692745e+13,
4.88788064793079335e+14,
-2.13203339609193739e+16,
];
pub(crate) fn gamln<T: BesselFloat>(z: T) -> Result<T, Error> {
let zero = T::zero();
let one = T::one();
if z <= zero {
return Err(Error::InvalidInput);
}
if z <= T::from_f64(101.0) {
let nz = z.floor().to_i32().unwrap();
let fz = z - T::from_f64(nz as f64);
if fz == zero && (1..=100).contains(&nz) {
return Ok(T::from_f64(GLN_TABLE[(nz - 1) as usize]));
}
}
let two = T::from_f64(2.0);
let wdtol = (two * T::MACH_EPSILON).max(T::from_f64(0.5e-18));
let r1m5 = T::from_f64(R1M5);
let rln = r1m5 * T::from_f64(T::MACH_DIGITS as f64);
let fln = rln.min(T::from_f64(20.0)).max(T::from_f64(3.0)) - T::from_f64(3.0);
let zm = T::from_f64(1.8) + T::from_f64(0.3875) * fln;
let mz = zm.to_i32().unwrap() + 1;
let zmin = T::from_f64(mz as f64);
let nz_trunc = z.floor().to_i32().unwrap(); let (zdmy, zinc) = if z < zmin {
let zinc_val = T::from_f64((mz - nz_trunc) as f64);
(z + zinc_val, zinc_val)
} else {
(z, zero)
};
let zp_init = one / zdmy;
let t1 = T::from_f64(CF_TABLE[0]) * zp_init;
let mut s = t1;
if zp_init >= wdtol {
let zsq = zp_init * zp_init;
let tst = t1 * wdtol;
let mut zp = zp_init;
for cf in &CF_TABLE[1..] {
zp = zp * zsq;
let trm = T::from_f64(*cf) * zp;
if trm.abs() < tst {
break;
}
s = s + trm;
}
}
let half = T::from_f64(0.5);
let con = T::from_f64(CON);
if zinc == zero {
let tlg = z.ln();
Ok(z * (tlg - one) + half * (con - tlg) + s)
} else {
let nz_int = zinc.to_i32().unwrap();
let mut product = one;
for i in 0..nz_int {
product = product * (z + T::from_f64(i.into()));
}
let tlg = zdmy.ln();
Ok(zdmy * (tlg - one) - product.ln() + half * (con - tlg) + s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gamln_integers() {
assert_eq!(gamln(1.0_f64).unwrap(), 0.0);
assert_eq!(gamln(2.0_f64).unwrap(), 0.0);
assert!((gamln(3.0_f64).unwrap() - 2.0_f64.ln()).abs() < 1e-15);
assert!((gamln(4.0_f64).unwrap() - 6.0_f64.ln()).abs() < 1e-14);
assert!((gamln(7.0_f64).unwrap() - 720.0_f64.ln()).abs() < 1e-13);
}
#[test]
fn gamln_table_boundary() {
let val = gamln(100.0_f64).unwrap();
assert!((val - 3.59134205369575399e+02).abs() < 1e-10);
let val101 = gamln(101.0_f64).unwrap();
assert!((val101 - 363.73937555556347).abs() < 1e-9);
}
#[test]
fn gamln_half_integer() {
let val = gamln(0.5_f64).unwrap();
let expected = 0.5 * core::f64::consts::PI.ln();
assert!((val - expected).abs() < 1e-14);
}
#[test]
fn gamln_small_positive() {
let val = gamln(0.1_f64).unwrap();
assert!((val - 2.2527126517342055).abs() < 1e-13);
}
#[test]
fn gamln_large() {
let val = gamln(150.0_f64).unwrap();
let mut expected = 0.0_f64;
for k in 1..150 {
expected += (k as f64).ln();
}
assert!((val - expected).abs() / expected < 1e-14);
}
#[test]
fn gamln_fractional_medium() {
let val = gamln(5.5_f64).unwrap();
let product: f64 = 4.5 * 3.5 * 2.5 * 1.5 * 0.5;
let expected = product.ln() + 0.5 * core::f64::consts::PI.ln();
assert!((val - expected).abs() < 1e-13);
}
#[test]
fn gamln_negative_returns_error() {
assert!(gamln(-1.0_f64).is_err());
assert!(gamln(0.0_f64).is_err());
}
#[test]
fn gamln_f32() {
let val = gamln(3.0_f32).unwrap();
assert!((val - 2.0_f32.ln()).abs() < 1e-6);
}
}