use super::exp::{exp_split, hi_lo_exp_inner_common};
use super::log::hi_lo_log_hi_lo_inner;
use super::scalbn;
use super::sin_cos::{hi_lo_cos_inner, hi_lo_sin_inner};
use super::{is_int, reduce_half_mul_pi, Exp, Log, ReduceHalfMulPi, SinCos};
use crate::double::{DenormDouble, NormDouble, SemiDouble};
use crate::traits::{Float, FloatConsts, Int as _, Like};
pub(crate) trait Gamma<L = Like<Self>>:
FloatConsts + SinCos + ReduceHalfMulPi + Exp + Log
{
fn lo_th() -> Self;
fn hi_th() -> Self;
fn th_1() -> Self;
fn th_2() -> Self;
fn th_3() -> Self;
const POLY_OFF: u8;
fn half_ln_2_pi() -> NormDouble<Self>;
fn lgamma_poly_1(x: Self) -> (Self, Self, Self, Self);
fn lgamma_poly_2(x: Self) -> (Self, Self, Self, Self);
fn special_poly(x: Self) -> Self;
}
pub(crate) fn tgamma<F: Gamma>(x: F) -> F {
let e = x.raw_exp();
if e == F::RawExp::ZERO && x.raw_mant() == F::Raw::ZERO {
F::INFINITY.copysign(x)
} else if x >= F::hi_th() {
F::INFINITY
} else if e == F::MAX_RAW_EXP {
F::NAN
} else if x.sign() && is_int(x) {
F::NAN
} else if x < F::lo_th() {
F::ZERO
} else {
tgamma_inner(x)
}
}
pub(crate) fn lgamma<F: Gamma>(x: F) -> (F, i8) {
let e = x.raw_exp();
let sign = x.sign();
if e == F::RawExp::ZERO && x.raw_mant() == F::Raw::ZERO {
(F::INFINITY, if sign { -1 } else { 1 })
} else if e == F::MAX_RAW_EXP {
if !sign && x.raw_mant() == F::Raw::ZERO {
(F::INFINITY, 1)
} else {
(F::NAN, 0)
}
} else if x.sign() && is_int(x) {
(F::INFINITY, 0)
} else if x == F::one() || x == F::two() {
(F::ZERO, 1)
} else {
lgamma_inner(x)
}
}
fn tgamma_inner<F: Gamma>(x: F) -> F {
let (y, s) = gamma_inner_common(x);
let (k, r_hi, r_lo) = exp_split(y.hi());
let r_lo = r_lo + y.lo();
let exp_y = hi_lo_exp_inner_common(r_hi, r_lo);
scalbn((s.to_semi() * exp_y.to_semi()).to_single(), k)
}
fn lgamma_inner<F: Gamma>(x: F) -> (F, i8) {
let (y, s) = gamma_inner_common(x);
if y.hi() == F::INFINITY {
(F::INFINITY, 1)
} else {
let s = s.to_norm();
let (sign, abs_s) = if s.hi().sign() { (-1, -s) } else { (1, s) };
let log_s = hi_lo_log_hi_lo_inner(abs_s, F::Exp::ZERO);
(log_s.qadd2(y).to_single(), sign)
}
}
fn gamma_inner_common<F: Gamma>(x: F) -> (DenormDouble<F>, DenormDouble<F>) {
let reflect = (x < F::half()).then(|| {
let (n, z) = reduce_half_mul_pi(x);
let sinpix = match n {
0 => hi_lo_sin_inner(z),
1 => hi_lo_cos_inner(z),
2 => -hi_lo_sin_inner(z),
3 => -hi_lo_cos_inner(z),
_ => unreachable!(),
};
F::pi_ex() / sinpix.to_semi()
});
let nx = if reflect.is_some() {
DenormDouble::new_sub11(F::one(), x)
} else {
DenormDouble::new(x, F::ZERO)
};
if nx.hi() < F::th_2() {
let nx = nx.hi();
let y;
let r;
let k1;
let k2;
let k3;
if nx < F::th_1() {
y = nx - F::one();
(r, k1, k2, k3) = F::lgamma_poly_1(y);
} else {
y = nx - F::two();
(r, k1, k2, k3) = F::lgamma_poly_2(y);
};
let r = finish_poly(y, r, k1, k2, k3);
if let Some(reflect) = reflect {
(-r, reflect)
} else {
(r, DenormDouble::one())
}
} else {
let low = nx.hi() < F::th_3();
let t = if low {
nx + F::cast_from(F::POLY_OFF)
} else {
nx
};
let tinv = F::one() / t.to_single();
let p1 = F::special_poly(tinv);
let p = SemiDouble::new(p1) * SemiDouble::new(tinv) + F::one();
let log_t = hi_lo_log_hi_lo_inner(t.to_norm(), F::Exp::ZERO);
let r = t.to_semi() * (log_t - F::one()).to_semi() - log_t.pmul1(F::half())
+ F::half_ln_2_pi().to_denorm();
let s = if low {
let mut den = nx;
for i in 1..F::POLY_OFF {
let nx_plus_i = nx + F::cast_from(i);
den = (den * nx_plus_i).normalize();
}
if let Some(reflect) = reflect {
(reflect * den) / p
} else {
p / den
}
} else if let Some(reflect) = reflect {
reflect / p
} else {
p
};
(if reflect.is_some() { -r } else { r }, s)
}
}
fn finish_poly<F: Float>(y: F, r: F, k1: F, k2: F, k3: F) -> DenormDouble<F> {
let y = SemiDouble::new(y);
let s = SemiDouble::new_qadd11(k3, r);
let t = y * s;
let s = SemiDouble::new_qadd12(k2, t);
let t = y * s;
let s = SemiDouble::new_qadd12(k1, t);
y * s
}
#[cfg(test)]
mod tests {
use crate::traits::Float;
use crate::FloatMath;
fn test_tgamma<F: Float + FloatMath>() {
use crate::tgamma;
assert_is_nan!(tgamma(F::NAN));
assert_is_nan!(tgamma(F::neg_infinity()));
assert_is_nan!(tgamma(-F::one()));
assert_is_nan!(tgamma(-F::two()));
assert_is_nan!(tgamma(-F::largest()));
assert_total_eq!(tgamma(F::INFINITY), F::INFINITY);
assert_total_eq!(tgamma(F::ZERO), F::INFINITY);
assert_total_eq!(tgamma(-F::ZERO), F::neg_infinity());
assert_total_eq!(tgamma(F::one()), F::one());
assert_total_eq!(tgamma(F::two()), F::one());
}
fn test_lgamma<F: Float + FloatMath>() {
use crate::lgamma;
let test_nan = |x: F| {
let (r, sign) = lgamma(x);
assert_is_nan!(r);
assert_eq!(sign, 0);
};
let test_value = |x: F, r: F, sign: i8| {
let (res, res_sign) = lgamma(x);
assert_total_eq!(res, r);
assert_eq!(res_sign, sign);
};
test_nan(F::NAN);
test_nan(F::neg_infinity());
test_value(F::INFINITY, F::INFINITY, 1);
test_value(F::ZERO, F::INFINITY, 1);
test_value(-F::ZERO, F::INFINITY, -1);
test_value(-F::one(), F::INFINITY, 0);
test_value(-F::two(), F::INFINITY, 0);
test_value(-F::largest(), F::INFINITY, 0);
test_value(F::one(), F::ZERO, 1);
test_value(F::two(), F::ZERO, 1);
}
#[test]
fn test_f32() {
test_tgamma::<f32>();
test_lgamma::<f32>();
}
#[cfg(feature = "soft-float")]
#[test]
fn test_soft_f32() {
test_tgamma::<crate::SoftF32>();
test_lgamma::<crate::SoftF32>();
}
#[test]
fn test_f64() {
test_tgamma::<f64>();
test_lgamma::<f64>();
}
#[cfg(feature = "soft-float")]
#[test]
fn test_soft_f64() {
test_tgamma::<crate::SoftF64>();
test_lgamma::<crate::SoftF64>();
}
}