use super::eval_pol;
use super::normal::dinvnr;
#[inline]
pub fn dt1(p: f64, q: f64, df: f64) -> f64 {
const COEF: [&[f64]; 4] = [
&[1.0, 1.0],
&[3.0, 16.0, 5.0],
&[-15.0, 17.0, 19.0, 3.0],
&[-945.0, -1920.0, 1482.0, 776.0, 79.0],
];
const DENOM: [f64; 4] = [4.0, 96.0, 384.0, 92160.0];
let x = dinvnr(p, q).abs();
let xx = x * x;
let mut sum1 = x;
let mut denpow = 1.0;
for i in 0..4 {
let term = eval_pol(COEF[i], xx) * x;
denpow *= df;
sum1 += term / (denpow * DENOM[i]);
}
if p >= 0.5 {
sum1
} else {
-sum1
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dt1_at_median_is_essentially_zero() {
for &df in &[1.0_f64, 5.0, 50.0, 1.0e6] {
let r = dt1(0.5, 0.5, df);
assert!(r.abs() < 1e-15, "df = {df}: dt1(0.5, 0.5) = {r}");
}
}
#[test]
fn dt1_symmetric_around_median() {
for &df in &[1.0_f64, 5.0, 50.0] {
for &p in &[0.1_f64, 0.3, 0.7, 0.9, 0.99] {
let a = dt1(p, 1.0 - p, df);
let b = dt1(1.0 - p, p, df);
assert!(
(a + b).abs() < 1e-13,
"p={p}, df={df}: dt1(p,1-p)={a}, dt1(1-p,p)={b}, sum={}",
a + b
);
}
}
}
#[test]
fn dt1_approaches_normal_quantile_for_large_df() {
for &(p, q) in &[(0.7_f64, 0.3), (0.9, 0.1), (0.975, 0.025), (0.999, 0.001)] {
let z = dinvnr(p, q);
let t = dt1(p, q, 1.0e6);
assert!((t - z).abs() < 1e-4, "p={p}: t = {t}, z = {z}");
}
}
#[test]
fn dt1_known_value_at_df_10() {
let r = dt1(0.975, 0.025, 10.0);
assert!((r - 2.228138851).abs() < 5e-3, "r = {r}");
}
#[test]
fn dt1_known_value_at_df_5() {
let r = dt1(0.975, 0.025, 5.0);
assert!((r - 2.5705818356).abs() < 5e-2, "r = {r}");
}
#[test]
fn dt1_sign_follows_p() {
assert!(dt1(0.3, 0.7, 5.0) < 0.0);
assert!(dt1(0.7, 0.3, 5.0) > 0.0);
assert!(dt1(0.001, 0.999, 5.0) < -1.0);
assert!(dt1(0.999, 0.001, 5.0) > 1.0);
}
}