use crate::common::{f_fmla, is_integer};
use crate::double_double::DoubleDouble;
use crate::logs::fast_log_dd;
use crate::polyeval::{f_polyeval6, f_polyeval7, f_polyeval8};
use crate::pow_exec::exp_dd_fast;
use crate::rounding::CpuFloor;
use crate::sincospi::f_fast_sinpi_dd;
pub fn f_tgamma(x: f64) -> f64 {
let x_a = f64::from_bits(x.to_bits() & 0x7fff_ffff_ffff_ffff);
if !x.is_normal() {
if x == 0.0 {
return f64::INFINITY;
}
if x.is_nan() {
return x + x;
}
if x.is_infinite() {
if x.is_sign_negative() {
return f64::NAN;
}
return x;
}
}
if x >= 171.624 {
return f64::INFINITY;
}
if is_integer(x) {
if x < 0. {
return f64::NAN;
}
if x < 38. {
let mut t = DoubleDouble::new(0., 1.);
let k = x as i64;
let mut x0 = 1.0;
for _i in 1..k {
t = DoubleDouble::quick_mult_f64(t, x0);
t = DoubleDouble::from_exact_add(t.hi, t.lo);
x0 += 1.0;
}
return t.to_f64();
}
}
if x <= -184.0 {
static SIGN: [f64; 2] = [
f64::from_bits(0x0010000000000000),
f64::from_bits(0x8010000000000000),
];
let k = x as i64;
return f64::from_bits(0x0010000000000000) * SIGN[((k & 1) != 0) as usize];
}
const EULER_DD: DoubleDouble =
DoubleDouble::from_bit_pair((0xbc56cb90701fbfab, 0x3fe2788cfc6fb619));
if x_a < 0.006 {
if x_a.to_bits() < (0x71e0000000000000u64 >> 1) {
return 1. / x;
}
if x_a < 2e-10 {
let p = DoubleDouble::full_dd_sub(DoubleDouble::from_quick_recip(x), EULER_DD);
return p.to_f64();
} else if x_a < 2e-6 {
const A2: DoubleDouble =
DoubleDouble::from_bit_pair((0x3c8dd92b465a8221, 0x3fefa658c23b1578));
let rcp = DoubleDouble::from_quick_recip(x);
let p = DoubleDouble::full_dd_add(DoubleDouble::mul_f64_add(A2, x, -EULER_DD), rcp);
return p.to_f64();
}
const C: [(u64, u64); 8] = [
(0x3c8dd92b465a8221, 0x3fefa658c23b1578),
(0x3c53a4f483760950, 0xbfed0a118f324b63),
(0x3c7fabe4f7369157, 0x3fef6a51055096b5),
(0x3c8c9fc795fc6142, 0xbfef6c80ec38b67b),
(0xbc5042339d62e721, 0x3fefc7e0a6eb310b),
(0xbc86fd0d8bdc0c1e, 0xbfefdf3f157b7a39),
(0xbc89b912df09395d, 0x3feff07b5a17ff6c),
(0x3c4e626faf780ff9, 0xbfeff803d68a0bd4),
];
let rcp = DoubleDouble::from_quick_recip(x);
let mut p = DoubleDouble::mul_f64_add(
DoubleDouble::from_bit_pair(C[7]),
x,
DoubleDouble::from_bit_pair(C[6]),
);
p = DoubleDouble::mul_f64_add(p, x, DoubleDouble::from_bit_pair(C[5]));
p = DoubleDouble::mul_f64_add(p, x, DoubleDouble::from_bit_pair(C[4]));
p = DoubleDouble::mul_f64_add(p, x, DoubleDouble::from_bit_pair(C[3]));
p = DoubleDouble::mul_f64_add(p, x, DoubleDouble::from_bit_pair(C[2]));
p = DoubleDouble::mul_f64_add(p, x, DoubleDouble::from_bit_pair(C[1]));
p = DoubleDouble::mul_f64_add(p, x, DoubleDouble::from_bit_pair(C[0]));
let z = DoubleDouble::mul_f64_add(p, x, DoubleDouble::full_dd_sub(rcp, EULER_DD));
return z.to_f64();
}
let mut fact = DoubleDouble::new(0., 0.0f64);
let mut parity = 1.0;
const PI: DoubleDouble = DoubleDouble::from_bit_pair((0x3ca1a62633145c07, 0x400921fb54442d18));
let mut dy = DoubleDouble::new(0., x);
let mut result: DoubleDouble;
if dy.hi < 0. {
if dy.hi.cpu_floor() == dy.hi {
return f64::NAN;
}
dy.hi = f64::from_bits(dy.hi.to_bits() & 0x7fff_ffff_ffff_ffff);
let y1 = x_a.cpu_floor();
let fraction = x_a - y1;
if fraction != 0.0
{
if y1 != (y1 * 0.5).cpu_floor() * 2.0 {
parity = -1.0;
}
fact = DoubleDouble::div(-PI, f_fast_sinpi_dd(fraction));
fact = DoubleDouble::from_exact_add(fact.hi, fact.lo);
dy = DoubleDouble::from_full_exact_add(dy.hi, 1.0);
}
}
if dy.hi < 12.0 {
let y1 = dy;
let z: DoubleDouble;
let mut n = 0;
if dy.hi < 1.0 {
z = dy;
dy = DoubleDouble::full_add_f64(dy, 1.0);
} else
{
n = dy.hi as i32 - 1;
dy = DoubleDouble::full_add_f64(dy, -n as f64);
z = DoubleDouble::full_add_f64(dy, -1.0);
}
let ps_num = f_polyeval8(
z.hi,
f64::from_bits(0x3fb38b80568a42aa),
f64::from_bits(0xbf8e7685b00d63a6),
f64::from_bits(0xbf80629ed2c48f1a),
f64::from_bits(0xbf6dfc4cdbcee96a),
f64::from_bits(0xbf471816939dc42b),
f64::from_bits(0xbf24bede7d8b3c20),
f64::from_bits(0xbeef56936d891e42),
f64::from_bits(0xbec3e2b405350813),
);
let mut p_num = DoubleDouble::mul_f64_add(
z,
ps_num,
DoubleDouble::from_bit_pair((0xbc700f441aea2edb, 0x3fd218bdde7878b8)),
);
p_num = DoubleDouble::mul_add(
z,
p_num,
DoubleDouble::from_bit_pair((0x3b7056a45a2fa50e, 0x3ff0000000000000)),
);
p_num = DoubleDouble::from_exact_add(p_num.hi, p_num.lo);
let ps_den = f_polyeval7(
z.hi,
f64::from_bits(0xbfdaa4f09f0caab1),
f64::from_bits(0xbfc960ba48423f9d),
f64::from_bits(0x3fb6873b64e8ccd6),
f64::from_bits(0x3f69ea1ca5b8a225),
f64::from_bits(0xbf77b166f68a2e63),
f64::from_bits(0x3f4fd1eff9193728),
f64::from_bits(0xbf0c1a43f4985c97),
);
let mut p_den = DoubleDouble::mul_f64_add(
z,
ps_den,
DoubleDouble::from_bit_pair((0xbc759594c51ad8b7, 0x3feb84ebebabf275)),
);
p_den = DoubleDouble::mul_add_f64(z, p_den, f64::from_bits(0x3ff0000000000000));
p_den = DoubleDouble::from_exact_add(p_den.hi, p_den.lo);
result = DoubleDouble::div(p_num, p_den);
if y1.hi < dy.hi {
result = DoubleDouble::div(result, y1);
} else if y1.hi > dy.hi {
for _ in 0..n {
result = DoubleDouble::mult(result, dy);
dy = DoubleDouble::full_add_f64(dy, 1.0);
}
}
} else {
if x > 171.624e+0 {
return f64::INFINITY;
}
let y_recip = dy.recip();
let y_sqr = DoubleDouble::mult(y_recip, y_recip);
let bernoulli_poly_s = f_polyeval6(
y_sqr.hi,
f64::from_bits(0xbf66c16c16c16c17),
f64::from_bits(0x3f4a01a01a01a01a),
f64::from_bits(0xbf43813813813814),
f64::from_bits(0x3f4b951e2b18ff23),
f64::from_bits(0xbf5f6ab0d9993c7d),
f64::from_bits(0x3f7a41a41a41a41a),
);
let bernoulli_poly = DoubleDouble::mul_f64_add(
y_sqr,
bernoulli_poly_s,
DoubleDouble::from_bit_pair((0x3c55555555555555, 0x3fb5555555555555)),
);
const LOG2_PI_OVER_2: DoubleDouble =
DoubleDouble::from_bit_pair((0xbc865b5a1b7ff5df, 0x3fed67f1c864beb5));
let mut log_gamma = DoubleDouble::add(
DoubleDouble::mul_add(bernoulli_poly, y_recip, -dy),
LOG2_PI_OVER_2,
);
let dy_log = fast_log_dd(dy);
log_gamma = DoubleDouble::mul_add(
DoubleDouble::from_exact_add(dy_log.hi, dy_log.lo),
DoubleDouble::add_f64(dy, -0.5),
log_gamma,
);
let log_prod = log_gamma.to_f64();
if log_prod >= 690. {
log_gamma = DoubleDouble::quick_mult_f64(log_gamma, 0.5);
result = exp_dd_fast(log_gamma);
let exp_result = result;
result.hi *= parity;
result.lo *= parity;
if fact.lo != 0. && fact.hi != 0. {
result = DoubleDouble::from_exact_add(result.hi, result.lo);
result = DoubleDouble::div(fact, result);
result = DoubleDouble::div(result, exp_result);
} else {
result = DoubleDouble::quick_mult(result, exp_result);
}
let err = f_fmla(
result.hi.abs(),
f64::from_bits(0x3c20000000000000), f64::from_bits(0x3bd0000000000000), );
let ub = result.hi + (result.lo + err);
let lb = result.hi + (result.lo - err);
if ub == lb {
return result.to_f64();
}
return result.to_f64();
}
result = exp_dd_fast(log_gamma);
}
if fact.lo != 0. && fact.hi != 0. {
result = DoubleDouble::from_exact_add(result.hi, result.lo);
result = DoubleDouble::div(fact, result);
}
result.to_f64() * parity
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tgamma() {
assert_eq!(f_tgamma(-1.70000000042915), 2.513923520668069);
assert_eq!(f_tgamma(5.), 24.);
assert_eq!(f_tgamma(24.), 25852016738884980000000.);
assert_eq!(f_tgamma(6.4324324), 255.1369211339094);
assert_eq!(f_tgamma(f64::INFINITY), f64::INFINITY);
assert_eq!(f_tgamma(0.), f64::INFINITY);
assert_eq!(f_tgamma(-0.), f64::INFINITY);
assert!(f_tgamma(f64::NAN).is_nan());
}
}