From 8deb334219e8bf1b4b68e5ad857ab653fea899dc Mon Sep 17 00:00:00 2001 From: Nick Thompson Date: Wed, 16 Sep 2026 21:13:40 -0700 Subject: [PATCH] Fix noncentral t degrees-of-freedom domain checking --- .../math/distributions/non_central_t.hpp | 20 +++++++++++++++++++ test/test_nc_t.hpp | 8 ++++++++ 2 files changed, 28 insertions(+) diff --git a/include/boost/math/distributions/non_central_t.hpp b/include/boost/math/distributions/non_central_t.hpp index dd873fa0cd..02c86d954d 100644 --- a/include/boost/math/distributions/non_central_t.hpp +++ b/include/boost/math/distributions/non_central_t.hpp @@ -772,6 +772,26 @@ namespace boost return policies::raise_evaluation_error(function, "Can't find degrees of freedom when the abscissa value is very close to zero as all degrees of freedom generate the same CDF at x=0: try again further out in the tails!!", RealType(std::numeric_limits::quiet_NaN()), Policy()); // LCOV_EXCL_LINE } + + // The limiting CDF values are + // + // v -> 0: Phi(-delta) + // v -> inf: Phi(x - delta) + // + // Outside the interval between these limits, a monotone CDF(v) + // has no inverse. In a non-monotone case there may instead be + // multiple inverses, so we cannot select a unique degree of freedom. + normal_distribution normal; + RealType p0 = cdf(normal, -delta); + RealType pinf = cdf(normal, x - delta); + RealType lower = p0 < pinf ? p0 : pinf; + RealType upper = p0 < pinf ? pinf : p0; + if((p < lower) || (p > upper)) + { + return policies::raise_evaluation_error(function, + "Probability %1% lies outside the interval between the limiting CDF values; the inverse is absent or non-unique", p, Policy()); + } + t_degrees_of_freedom_finder f(delta, x, p < q ? p : q, p < q ? false : true); tools::eps_tolerance tol(policies::digits()); std::uintmax_t max_iter = policies::get_max_root_iterations(); diff --git a/test/test_nc_t.hpp b/test/test_nc_t.hpp index 6d4db2166a..00675426f1 100644 --- a/test/test_nc_t.hpp +++ b/test/test_nc_t.hpp @@ -350,6 +350,14 @@ void test_spots(RealType) BOOST_CHECK_CLOSE_FRACTION(cdf(d, -1), static_cast(1.61471461239552e-127), 1e-3); } + // https://github.com/boostorg/math/issues/1410 + // p=0.99 is below both limiting CDF values: + // Phi(2.5) and Phi(3.75). Previously this could return a tiny, + // meaningless degree of freedom instead of reporting that the inverse + // cannot be selected uniquely. + BOOST_MATH_CHECK_THROW(distro1::find_degrees_of_freedom(static_cast(-2.5), static_cast(1.25), static_cast(0.99)), boost::math::evaluation_error); + BOOST_MATH_CHECK_THROW(distro1::find_degrees_of_freedom(boost::math::complement(static_cast(-2.5), static_cast(1.25), static_cast(0.01))), boost::math::evaluation_error); + } // template void test_spots(RealType) template