fix(stats): hold the Student-t tails past t-squared overflow

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-27 17:03:36 +02:00
parent 2f9358c512
commit 275de79123
6 changed files with 113 additions and 10 deletions
+11 -5
View File
@@ -269,15 +269,18 @@ func StudentTCDF(t float64, df int) (float64, error) {
if df < 1 {
return 0, base.Errf("StudentTCDF: df must be ≥ 1, got %d", df)
}
z := float64(df) / (float64(df) + t*t)
upper, err := BetaIncomplete(z, float64(df)/2, 0.5)
// One house tail: the upper-tail helper carries the asymptotic forms
// the heavy df ≤ 2 laws keep past t²'s overflow, where the closed
// form's z = df/(df+t²) collapses to 0 and the lower tail answered a
// silent 0 for a tail the format still holds.
upper, err := studentTUpperTail(math.Abs(t), df)
if err != nil {
return 0, base.Errf("StudentTCDF: %w", err)
}
if t >= 0 {
return 1 - upper/2, nil
return 1 - upper, nil
}
return upper / 2, nil
return upper, nil
}
// PoissonCDF returns P(N ≤ k) for N ~ Poisson(lambda), through the
@@ -701,7 +704,10 @@ func studentTUpperTail(t float64, df int) (float64, error) {
// huge t keeps accurate.
return 1 / (math.Pi * t), nil
case df == 2:
return 1 / (2 * t * t), nil
// The tail 1/(2t²) divided one t at a time: the literal
// denominator overflows past √MaxFloat64, and the quotient
// would flush the still-representable tail to zero.
return 0.5 / t / t, nil
}
}
z := float64(df) / (float64(df) + t*t)