Tests: Derive the subnormal betainc tolerance from the exponent's terms libeigen/eigen!3435 Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
diff --git a/contrib/test/special_functions.cpp b/contrib/test/special_functions.cpp index cc044c9..7a9beec 100644 --- a/contrib/test/special_functions.cpp +++ b/contrib/test/special_functions.cpp
@@ -577,12 +577,19 @@ VERIFY((numext::isnan)(numext::betainc(nan, Scalar(0.5), Scalar(0)))); if (sizeof(Scalar) < sizeof(double)) { - // MPFR-backed Boost.Math ibeta, 100 decimal digits. The log-domain evaluation at a ~= 38 amplifies rounding; - // 32 eps bounds the scalar error here, plus one subnormal ulp for the final conversion. + // MPFR-backed Boost.Math ibeta, 100 decimal digits. The result is exp(t) with + // t = b log(1 - x) + a' log(x) + lgamma(a' + b) - lgamma(a') - lgamma(b) + log(cf / a'), a' = a + 1, summed in + // Scalar: |t| ~ 90, but its first five terms add up to S ~ 330 in magnitude, so t carries an absolute rounding + // error of order S eps, which exp turns into relative error. How much of it is reached depends on the libm (the + // result is off by 62 eps with glibc 2.43), hence S eps rather than a measured constant, plus one subnormal ulp for + // the final rounding. const Scalar a = Scalar(38.325092315673828), b = Scalar(5.0568113327026367), x = Scalar(0.07500767707824707); const double ref = 6.17294757793914262e-40; const double actual = static_cast<double>(numext::betainc(a + Scalar(1), b, x)); - VERIFY(std::abs(actual - ref) <= 32 * static_cast<double>(NumTraits<Scalar>::epsilon()) * ref + + const double ap = static_cast<double>(a) + 1, bd = static_cast<double>(b), xd = static_cast<double>(x); + const double S = bd * std::abs(std::log1p(-xd)) + ap * std::abs(std::log(xd)) + std::lgamma(ap + bd) + + std::lgamma(ap) + std::abs(std::lgamma(bd)); + VERIFY(std::abs(actual - ref) <= S * static_cast<double>(NumTraits<Scalar>::epsilon()) * ref + static_cast<double>((std::numeric_limits<Scalar>::denorm_min)())); }