Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions include/boost/math/distributions/students_t.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ BOOST_MATH_GPU_ENABLED inline RealType pdf(const students_t_distribution<RealTyp
}
else
{ //
if (fabs(x) > sqrt(tools::max_value<RealType>()))
{
// Exact limit: the pdf underflows to zero long before x * x can overflow.
return 0;
}
RealType basem1 = x * x / df;
if(basem1 < 0.125)
{
Expand All @@ -146,6 +151,8 @@ BOOST_MATH_GPU_ENABLED inline RealType pdf(const students_t_distribution<RealTyp
template <class RealType, class Policy>
BOOST_MATH_GPU_ENABLED inline RealType cdf(const students_t_distribution<RealType, Policy>& 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();
Expand Down Expand Up @@ -201,6 +208,11 @@ BOOST_MATH_GPU_ENABLED inline RealType cdf(const students_t_distribution<RealTyp
//
// 1 - x = t^2 / (df + t^2)
//
if (fabs(x) > sqrt(tools::max_value<RealType>()))
{
// Exact tail values: avoids a spurious intermediate overflow in x * x below.
return (x < 0) ? static_cast<RealType>(0) : static_cast<RealType>(1);
}
RealType x2 = x * x;
RealType probability;
if(df > 2 * x2)
Expand Down
1 change: 1 addition & 0 deletions test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -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 ]
Expand Down
133 changes: 133 additions & 0 deletions test/test_students_t_overflow.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// 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 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 <boost/test/unit_test.hpp>
#include <boost/test/tools/floating_point_comparison.hpp>
#include <boost/math/tools/test.hpp>

#include <boost/math/distributions/students_t.hpp>
#include <boost/math/special_functions/fpclassify.hpp>
#include <boost/math/tools/precision.hpp>

#ifndef BOOST_MATH_STANDALONE
#include <boost/math/concepts/real_concept.hpp> // for real_concept
#include <boost/multiprecision/cpp_bin_float.hpp> // for cpp_bin_float_50
#endif

#include <cfenv>
#include <cmath>
#include <limits>

#pragma STDC FENV_ACCESS ON

template <class RealType>
RealType overflowing_deviate()
{
BOOST_MATH_STD_USING // ADL sqrt for built-in and UDT types
using boost::math::tools::max_value;
return 2 * sqrt(max_value<RealType>());
}

// 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 <class RealType>
void test_tail_values(RealType)
{
using namespace boost::math;
using namespace boost::math::policies;

typedef policy<overflow_error<throw_on_error> > strict_policy;
typedef students_t_distribution<RealType, strict_policy> dist_t;

dist_t dist(static_cast<RealType>(5));
const RealType big = overflowing_deviate<RealType>();

BOOST_CHECK((boost::math::isfinite)(big));

BOOST_CHECK_EQUAL(pdf(dist, big), static_cast<RealType>(0));
BOOST_CHECK_EQUAL(pdf(dist, -big), static_cast<RealType>(0));

BOOST_CHECK_EQUAL(cdf(dist, big), static_cast<RealType>(1));
BOOST_CHECK_EQUAL(cdf(dist, -big), static_cast<RealType>(0));

BOOST_CHECK_EQUAL(cdf(complement(dist, big)), static_cast<RealType>(0));
BOOST_CHECK_EQUAL(cdf(complement(dist, -big)), static_cast<RealType>(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 <class RealType>
void test_no_fp_exceptions(RealType)
{
using namespace boost::math;

students_t_distribution<RealType> dist(static_cast<RealType>(5));
const RealType big = overflowing_deviate<RealType>();

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<RealType>(0));
BOOST_CHECK_EQUAL(c1, static_cast<RealType>(1));
BOOST_CHECK_EQUAL(c2, static_cast<RealType>(0));
}

// Ordinary arguments must be unaffected by the guard.
template <class RealType>
void test_no_regression(RealType)
{
using namespace boost::math;

students_t_distribution<RealType> dist(static_cast<RealType>(10));
const RealType tol = boost::math::tools::epsilon<RealType>() * 100;

BOOST_CHECK_CLOSE_FRACTION(cdf(dist, static_cast<RealType>(0)),
static_cast<RealType>(0.5), tol);

RealType p = pdf(dist, static_cast<RealType>(1.5));
BOOST_CHECK((boost::math::isfinite)(p));
BOOST_CHECK(p > 0);

RealType big_but_safe = static_cast<RealType>(1e6);
BOOST_CHECK_CLOSE_FRACTION(cdf(dist, big_but_safe),
static_cast<RealType>(1), tol);
}

BOOST_AUTO_TEST_CASE(students_t_overflow_test)
Comment thread
mborland marked this conversation as resolved.
{
test_tail_values(0.0F);
test_tail_values(0.0);
test_tail_values(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);

// real_concept and multiprecision compute distribution constants via
// lexical_cast, which standalone mode disables, so both are skipped there.
#ifndef BOOST_MATH_STANDALONE
test_tail_values(boost::math::concepts::real_concept(0));
test_no_regression(boost::math::concepts::real_concept(0));

test_tail_values(boost::multiprecision::cpp_bin_float_50(0));
test_no_regression(boost::multiprecision::cpp_bin_float_50(0));
#endif
}
Loading