From 4d863b1722f7038113f5abba9765085a676743d0 Mon Sep 17 00:00:00 2001 From: LeantionX Date: Wed, 3 Jun 2026 10:07:21 +0300 Subject: [PATCH 1/7] Guard against intermediate x*x overflow in Student's t pdf/cdf pdf() and cdf() form x*x before normalising by the degrees of freedom. For finite but large |x| this overflows to infinity, after which pdf() silently returns 0 and cdf() silently returns the tail, bypassing the error-handling policy. Add an explicit guard that invokes raise_overflow_error<> via the active Policy, mirroring quantile(). Adds test_students_t_overflow.cpp covering throw_on_error, errno_on_error and a no-regression case for float/double/long double. --- .../boost/math/distributions/students_t.hpp | 10 ++ test/Jamfile.v2 | 1 + test/test_students_t_overflow.cpp | 117 ++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 test/test_students_t_overflow.cpp diff --git a/include/boost/math/distributions/students_t.hpp b/include/boost/math/distributions/students_t.hpp index bc1c7139eb..6c0ec8e3dc 100644 --- a/include/boost/math/distributions/students_t.hpp +++ b/include/boost/math/distributions/students_t.hpp @@ -129,6 +129,11 @@ BOOST_MATH_GPU_ENABLED inline RealType pdf(const students_t_distribution sqrt(tools::max_value())) + { + return policies::raise_overflow_error( + "boost::math::pdf(const students_t_distribution<%1%>&, %1%)", 0, Policy()); + } RealType basem1 = x * x / df; if(basem1 < 0.125) { @@ -201,6 +206,11 @@ BOOST_MATH_GPU_ENABLED inline RealType cdf(const students_t_distribution sqrt(tools::max_value())) + { + return policies::raise_overflow_error( + "boost::math::cdf(const students_t_distribution<%1%>&, %1%)", 0, Policy()); + } RealType x2 = x * x; RealType probability; if(df > 2 * x2) diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index c5bde01635..5b67e474df 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -895,6 +895,7 @@ test-suite distribution_tests : : test_poisson_real_concept ] [ run test_rayleigh.cpp /boost/test//boost_unit_test_framework ] [ run test_students_t.cpp /boost/test//boost_unit_test_framework ] + [ run test_students_t_overflow.cpp /boost/test//boost_unit_test_framework ] [ run test_skew_normal.cpp /boost/test//boost_unit_test_framework ] [ run test_triangular.cpp pch /boost/test//boost_unit_test_framework ] [ run test_uniform.cpp pch /boost/test//boost_unit_test_framework ] diff --git a/test/test_students_t_overflow.cpp b/test/test_students_t_overflow.cpp new file mode 100644 index 0000000000..b5f54b5106 --- /dev/null +++ b/test/test_students_t_overflow.cpp @@ -0,0 +1,117 @@ +// Copyright Anton Leontev 2026. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// Regression test for intermediate overflow of x*x in the Student's t +// distribution pdf() and cdf(). + +#define BOOST_TEST_MAIN +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +template +RealType overflowing_deviate() +{ + using boost::math::tools::max_value; + return 2 * std::sqrt(max_value()); +} + +template +void test_throws(RealType) +{ + using namespace boost::math; + using namespace boost::math::policies; + + typedef policy > throw_policy; + typedef students_t_distribution dist_t; + + dist_t dist(static_cast(5)); + const RealType big = overflowing_deviate(); + + BOOST_CHECK((boost::math::isfinite)(big)); + + BOOST_CHECK_THROW(pdf(dist, big), std::overflow_error); + BOOST_CHECK_THROW(pdf(dist, -big), std::overflow_error); + BOOST_CHECK_THROW(cdf(dist, big), std::overflow_error); + BOOST_CHECK_THROW(cdf(dist, -big), std::overflow_error); +} + +template +void test_errno(RealType) +{ + using namespace boost::math; + using namespace boost::math::policies; + + typedef policy > errno_policy; + typedef students_t_distribution dist_t; + + dist_t dist(static_cast(5)); + const RealType big = overflowing_deviate(); + + errno = 0; + RealType r = pdf(dist, big); + BOOST_CHECK_EQUAL(errno, ERANGE); + BOOST_CHECK(!(boost::math::isfinite)(r)); + + errno = 0; + r = cdf(dist, big); + BOOST_CHECK_EQUAL(errno, ERANGE); + BOOST_CHECK(!(boost::math::isfinite)(r)); +} + +template +void test_no_regression(RealType) +{ + using namespace boost::math; + using namespace boost::math::policies; + + typedef policy > errno_policy; + typedef students_t_distribution dist_t; + + dist_t dist(static_cast(10)); + const RealType tol = boost::math::tools::epsilon() * 100; + + errno = 0; + BOOST_CHECK_CLOSE_FRACTION(cdf(dist, static_cast(0)), + static_cast(0.5), tol); + BOOST_CHECK_EQUAL(errno, 0); + + errno = 0; + RealType p = pdf(dist, static_cast(1.5)); + BOOST_CHECK_EQUAL(errno, 0); + BOOST_CHECK((boost::math::isfinite)(p)); + BOOST_CHECK(p > 0); + + errno = 0; + RealType big_but_safe = static_cast(1e6); + RealType c = cdf(dist, big_but_safe); + BOOST_CHECK_EQUAL(errno, 0); + BOOST_CHECK_CLOSE_FRACTION(c, static_cast(1), tol); +} + +BOOST_AUTO_TEST_CASE(students_t_overflow_test) +{ + test_throws(0.0F); + test_throws(0.0); + test_throws(0.0L); + + test_errno(0.0F); + test_errno(0.0); + test_errno(0.0L); + + test_no_regression(0.0F); + test_no_regression(0.0); + test_no_regression(0.0L); +} From bc966c5dfd62255fdbb7ed4226e1d2a9c4726726 Mon Sep 17 00:00:00 2001 From: LeantionX Date: Wed, 3 Jun 2026 13:56:51 +0300 Subject: [PATCH 2/7] Use std::fabs to avoid -Wabsolute-value truncation on long double --- include/boost/math/distributions/students_t.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/math/distributions/students_t.hpp b/include/boost/math/distributions/students_t.hpp index 6c0ec8e3dc..44b95c8861 100644 --- a/include/boost/math/distributions/students_t.hpp +++ b/include/boost/math/distributions/students_t.hpp @@ -129,7 +129,7 @@ BOOST_MATH_GPU_ENABLED inline RealType pdf(const students_t_distribution sqrt(tools::max_value())) + if (std::fabs(x) > sqrt(tools::max_value())) { return policies::raise_overflow_error( "boost::math::pdf(const students_t_distribution<%1%>&, %1%)", 0, Policy()); @@ -206,7 +206,7 @@ BOOST_MATH_GPU_ENABLED inline RealType cdf(const students_t_distribution sqrt(tools::max_value())) + if (std::fabs(x) > sqrt(tools::max_value())) { return policies::raise_overflow_error( "boost::math::cdf(const students_t_distribution<%1%>&, %1%)", 0, Policy()); From 892b4cef0e8f29ddceb6e24a477d169c27c0cfa2 Mon Sep 17 00:00:00 2001 From: LeantionX Date: Wed, 3 Jun 2026 16:35:05 +0300 Subject: [PATCH 3/7] Use ADL fabs and extend overflow test to non-standard types Address review feedback: - Revert std::fabs to an ADL fabs in the pdf/cdf guards and add BOOST_MATH_STD_USING to cdf, so the call resolves for non-standard types (real_concept, multiprecision). - Extend test_students_t_overflow with real_concept and cpp_bin_float_50, exercising the throw policy and the no-regression path. The test's overflowing_deviate() now also uses an ADL sqrt for the same reason. --- include/boost/math/distributions/students_t.hpp | 6 ++++-- test/test_students_t_overflow.cpp | 14 +++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/include/boost/math/distributions/students_t.hpp b/include/boost/math/distributions/students_t.hpp index 44b95c8861..3525214c25 100644 --- a/include/boost/math/distributions/students_t.hpp +++ b/include/boost/math/distributions/students_t.hpp @@ -129,7 +129,7 @@ BOOST_MATH_GPU_ENABLED inline RealType pdf(const students_t_distribution sqrt(tools::max_value())) + if (fabs(x) > sqrt(tools::max_value())) { return policies::raise_overflow_error( "boost::math::pdf(const students_t_distribution<%1%>&, %1%)", 0, Policy()); @@ -151,6 +151,8 @@ BOOST_MATH_GPU_ENABLED inline RealType pdf(const students_t_distribution BOOST_MATH_GPU_ENABLED inline RealType cdf(const students_t_distribution& dist, const RealType& x) { + BOOST_MATH_STD_USING // for ADL of std functions + RealType error_result; // degrees_of_freedom > 0 or infinity check: RealType df = dist.degrees_of_freedom(); @@ -206,7 +208,7 @@ BOOST_MATH_GPU_ENABLED inline RealType cdf(const students_t_distribution sqrt(tools::max_value())) + if (fabs(x) > sqrt(tools::max_value())) { return policies::raise_overflow_error( "boost::math::cdf(const students_t_distribution<%1%>&, %1%)", 0, Policy()); diff --git a/test/test_students_t_overflow.cpp b/test/test_students_t_overflow.cpp index b5f54b5106..2301d56f55 100644 --- a/test/test_students_t_overflow.cpp +++ b/test/test_students_t_overflow.cpp @@ -14,6 +14,8 @@ #include #include +#include // for real_concept +#include // for cpp_bin_float_50 #include #include @@ -24,8 +26,9 @@ template RealType overflowing_deviate() { + BOOST_MATH_STD_USING // pull in std::sqrt for built-in types via ADL using boost::math::tools::max_value; - return 2 * std::sqrt(max_value()); + return 2 * sqrt(max_value()); } template @@ -114,4 +117,13 @@ BOOST_AUTO_TEST_CASE(students_t_overflow_test) test_no_regression(0.0F); test_no_regression(0.0); test_no_regression(0.0L); + + // Non-standard types: these must compile (exercises ADL fabs) and behave. + // errno semantics are only meaningful for built-in types, so only the + // policy-driven throw and the no-regression cases are exercised here. + test_throws(boost::math::concepts::real_concept(0)); + test_no_regression(boost::math::concepts::real_concept(0)); + + test_throws(boost::multiprecision::cpp_bin_float_50(0)); + test_no_regression(boost::multiprecision::cpp_bin_float_50(0)); } From efa85883e4ecc0d071935441b1add3c55cbb7355 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 3 Jun 2026 11:40:23 -0400 Subject: [PATCH 4/7] Disable testing of boost::mp in standalone mode --- test/test_students_t_overflow.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/test_students_t_overflow.cpp b/test/test_students_t_overflow.cpp index 2301d56f55..385798da60 100644 --- a/test/test_students_t_overflow.cpp +++ b/test/test_students_t_overflow.cpp @@ -15,10 +15,13 @@ #include #include #include // for real_concept -#include // for cpp_bin_float_50 #include #include +#ifndef BOOST_MATH_STANDALONE +#include // for cpp_bin_float_50 +#endif + #include #include #include @@ -124,6 +127,8 @@ BOOST_AUTO_TEST_CASE(students_t_overflow_test) test_throws(boost::math::concepts::real_concept(0)); test_no_regression(boost::math::concepts::real_concept(0)); + #ifndef BOOST_MATH_STANDALONE test_throws(boost::multiprecision::cpp_bin_float_50(0)); test_no_regression(boost::multiprecision::cpp_bin_float_50(0)); + #endif } From 01e41d555f548f2d21db01c5d40f2452ef369e9a Mon Sep 17 00:00:00 2001 From: Anton Leontev Date: Wed, 3 Jun 2026 19:49:32 +0300 Subject: [PATCH 5/7] Also skip real_concept in standalone mode (lexical_cast unavailable) --- test/test_students_t_overflow.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/test_students_t_overflow.cpp b/test/test_students_t_overflow.cpp index 385798da60..f3856de6e6 100644 --- a/test/test_students_t_overflow.cpp +++ b/test/test_students_t_overflow.cpp @@ -14,11 +14,11 @@ #include #include -#include // for real_concept #include #include #ifndef BOOST_MATH_STANDALONE +#include // for real_concept #include // for cpp_bin_float_50 #endif @@ -124,10 +124,12 @@ BOOST_AUTO_TEST_CASE(students_t_overflow_test) // Non-standard types: these must compile (exercises ADL fabs) and behave. // errno semantics are only meaningful for built-in types, so only the // policy-driven throw and the no-regression cases are exercised here. + // real_concept and multiprecision compute distribution constants via + // lexical_cast, which standalone mode disables, so both are skipped there. + #ifndef BOOST_MATH_STANDALONE test_throws(boost::math::concepts::real_concept(0)); test_no_regression(boost::math::concepts::real_concept(0)); - #ifndef BOOST_MATH_STANDALONE test_throws(boost::multiprecision::cpp_bin_float_50(0)); test_no_regression(boost::multiprecision::cpp_bin_float_50(0)); #endif From 455bee36249e2b0271a096a62a4737e817b8bd00 Mon Sep 17 00:00:00 2001 From: Anton Leontev Date: Thu, 4 Jun 2026 08:45:56 +0300 Subject: [PATCH 6/7] Drop errno==0 assertions on success paths (UBSan report printing clobbers errno) --- test/test_students_t_overflow.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/test_students_t_overflow.cpp b/test/test_students_t_overflow.cpp index f3856de6e6..2499e369f5 100644 --- a/test/test_students_t_overflow.cpp +++ b/test/test_students_t_overflow.cpp @@ -89,21 +89,20 @@ void test_no_regression(RealType) dist_t dist(static_cast(10)); const RealType tol = boost::math::tools::epsilon() * 100; - errno = 0; + // No errno assertions on these success paths: C11 7.5/3 allows any + // library call to set errno even on success, and under UBSan the + // diagnostic printer itself clobbers errno (isatty() on a redirected + // stderr yields ENOTTY). Policy-driven errno semantics are verified + // separately in test_errno(). BOOST_CHECK_CLOSE_FRACTION(cdf(dist, static_cast(0)), static_cast(0.5), tol); - BOOST_CHECK_EQUAL(errno, 0); - errno = 0; RealType p = pdf(dist, static_cast(1.5)); - BOOST_CHECK_EQUAL(errno, 0); BOOST_CHECK((boost::math::isfinite)(p)); BOOST_CHECK(p > 0); - errno = 0; RealType big_but_safe = static_cast(1e6); RealType c = cdf(dist, big_but_safe); - BOOST_CHECK_EQUAL(errno, 0); BOOST_CHECK_CLOSE_FRACTION(c, static_cast(1), tol); } From 1e7a2c76c8c1b517f4a3395e5d43b454e3670032 Mon Sep 17 00:00:00 2001 From: Anton Leontev Date: Thu, 4 Jun 2026 19:27:12 +0300 Subject: [PATCH 7/7] Return exact tail values instead of raising overflow_error (review feedback) --- .../boost/math/distributions/students_t.hpp | 8 +- test/test_students_t_overflow.cpp | 102 +++++++++--------- 2 files changed, 54 insertions(+), 56 deletions(-) diff --git a/include/boost/math/distributions/students_t.hpp b/include/boost/math/distributions/students_t.hpp index 3525214c25..69e5dde43c 100644 --- a/include/boost/math/distributions/students_t.hpp +++ b/include/boost/math/distributions/students_t.hpp @@ -131,8 +131,8 @@ BOOST_MATH_GPU_ENABLED inline RealType pdf(const students_t_distribution sqrt(tools::max_value())) { - return policies::raise_overflow_error( - "boost::math::pdf(const students_t_distribution<%1%>&, %1%)", 0, Policy()); + // Exact limit: the pdf underflows to zero long before x * x can overflow. + return 0; } RealType basem1 = x * x / df; if(basem1 < 0.125) @@ -210,8 +210,8 @@ BOOST_MATH_GPU_ENABLED inline RealType cdf(const students_t_distribution sqrt(tools::max_value())) { - return policies::raise_overflow_error( - "boost::math::cdf(const students_t_distribution<%1%>&, %1%)", 0, Policy()); + // Exact tail values: avoids a spurious intermediate overflow in x * x below. + return (x < 0) ? static_cast(0) : static_cast(1); } RealType x2 = x * x; RealType probability; diff --git a/test/test_students_t_overflow.cpp b/test/test_students_t_overflow.cpp index 2499e369f5..d4020d3bf4 100644 --- a/test/test_students_t_overflow.cpp +++ b/test/test_students_t_overflow.cpp @@ -4,8 +4,11 @@ // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // -// Regression test for intermediate overflow of x*x in the Student's t -// distribution pdf() and cdf(). +// Regression test for spurious intermediate overflow of x*x in the +// Student's t pdf() and cdf(): for |x| > sqrt(max_value) the functions +// must return the exact tail values (0 for the pdf, 0/1 for the cdf and +// its complement) without raising FE_OVERFLOW and without invoking any +// error handler. #define BOOST_TEST_MAIN #include @@ -13,7 +16,6 @@ #include #include -#include #include #include @@ -22,78 +24,77 @@ #include // for cpp_bin_float_50 #endif -#include +#include #include #include +#pragma STDC FENV_ACCESS ON + template RealType overflowing_deviate() { - BOOST_MATH_STD_USING // pull in std::sqrt for built-in types via ADL + BOOST_MATH_STD_USING // ADL sqrt for built-in and UDT types using boost::math::tools::max_value; return 2 * sqrt(max_value()); } +// For |x| > sqrt(max_value) the mathematically exact results have already +// saturated: pdf == 0, cdf == 0 or 1. These must be returned directly. +// A throwing policy is used so that any error-handler invocation would +// fail the test with an unexpected exception. template -void test_throws(RealType) +void test_tail_values(RealType) { using namespace boost::math; using namespace boost::math::policies; - typedef policy > throw_policy; - typedef students_t_distribution dist_t; + typedef policy > strict_policy; + typedef students_t_distribution dist_t; dist_t dist(static_cast(5)); const RealType big = overflowing_deviate(); BOOST_CHECK((boost::math::isfinite)(big)); - BOOST_CHECK_THROW(pdf(dist, big), std::overflow_error); - BOOST_CHECK_THROW(pdf(dist, -big), std::overflow_error); - BOOST_CHECK_THROW(cdf(dist, big), std::overflow_error); - BOOST_CHECK_THROW(cdf(dist, -big), std::overflow_error); + BOOST_CHECK_EQUAL(pdf(dist, big), static_cast(0)); + BOOST_CHECK_EQUAL(pdf(dist, -big), static_cast(0)); + + BOOST_CHECK_EQUAL(cdf(dist, big), static_cast(1)); + BOOST_CHECK_EQUAL(cdf(dist, -big), static_cast(0)); + + BOOST_CHECK_EQUAL(cdf(complement(dist, big)), static_cast(0)); + BOOST_CHECK_EQUAL(cdf(complement(dist, -big)), static_cast(1)); } +// Built-in types: the calls must not leave FE_OVERFLOW set -- avoiding the +// spurious intermediate overflow is the whole point of the guard. template -void test_errno(RealType) +void test_no_fp_exceptions(RealType) { using namespace boost::math; - using namespace boost::math::policies; - - typedef policy > errno_policy; - typedef students_t_distribution dist_t; - dist_t dist(static_cast(5)); + students_t_distribution dist(static_cast(5)); const RealType big = overflowing_deviate(); - errno = 0; - RealType r = pdf(dist, big); - BOOST_CHECK_EQUAL(errno, ERANGE); - BOOST_CHECK(!(boost::math::isfinite)(r)); - - errno = 0; - r = cdf(dist, big); - BOOST_CHECK_EQUAL(errno, ERANGE); - BOOST_CHECK(!(boost::math::isfinite)(r)); + std::feclearexcept(FE_ALL_EXCEPT); + RealType p = pdf(dist, big); + RealType c1 = cdf(dist, big); + RealType c2 = cdf(dist, -big); + BOOST_CHECK_EQUAL(std::fetestexcept(FE_OVERFLOW), 0); + BOOST_CHECK_EQUAL(p, static_cast(0)); + BOOST_CHECK_EQUAL(c1, static_cast(1)); + BOOST_CHECK_EQUAL(c2, static_cast(0)); } +// Ordinary arguments must be unaffected by the guard. template void test_no_regression(RealType) { using namespace boost::math; - using namespace boost::math::policies; - - typedef policy > errno_policy; - typedef students_t_distribution dist_t; - dist_t dist(static_cast(10)); + students_t_distribution dist(static_cast(10)); const RealType tol = boost::math::tools::epsilon() * 100; - // No errno assertions on these success paths: C11 7.5/3 allows any - // library call to set errno even on success, and under UBSan the - // diagnostic printer itself clobbers errno (isatty() on a redirected - // stderr yields ENOTTY). Policy-driven errno semantics are verified - // separately in test_errno(). BOOST_CHECK_CLOSE_FRACTION(cdf(dist, static_cast(0)), static_cast(0.5), tol); @@ -102,34 +103,31 @@ void test_no_regression(RealType) BOOST_CHECK(p > 0); RealType big_but_safe = static_cast(1e6); - RealType c = cdf(dist, big_but_safe); - BOOST_CHECK_CLOSE_FRACTION(c, static_cast(1), tol); + BOOST_CHECK_CLOSE_FRACTION(cdf(dist, big_but_safe), + static_cast(1), tol); } BOOST_AUTO_TEST_CASE(students_t_overflow_test) { - test_throws(0.0F); - test_throws(0.0); - test_throws(0.0L); + test_tail_values(0.0F); + test_tail_values(0.0); + test_tail_values(0.0L); - test_errno(0.0F); - test_errno(0.0); - test_errno(0.0L); + test_no_fp_exceptions(0.0F); + test_no_fp_exceptions(0.0); + test_no_fp_exceptions(0.0L); test_no_regression(0.0F); test_no_regression(0.0); test_no_regression(0.0L); - // Non-standard types: these must compile (exercises ADL fabs) and behave. - // errno semantics are only meaningful for built-in types, so only the - // policy-driven throw and the no-regression cases are exercised here. // real_concept and multiprecision compute distribution constants via // lexical_cast, which standalone mode disables, so both are skipped there. - #ifndef BOOST_MATH_STANDALONE - test_throws(boost::math::concepts::real_concept(0)); +#ifndef BOOST_MATH_STANDALONE + test_tail_values(boost::math::concepts::real_concept(0)); test_no_regression(boost::math::concepts::real_concept(0)); - test_throws(boost::multiprecision::cpp_bin_float_50(0)); + test_tail_values(boost::multiprecision::cpp_bin_float_50(0)); test_no_regression(boost::multiprecision::cpp_bin_float_50(0)); - #endif +#endif }