Skip to content

Commit 09d82da

Browse files
authored
Merge pull request #1026 from boostorg/issue1006
Avoid spurious overflow and divide by zero in ibeta.
2 parents ce18ea4 + 2d9d202 commit 09d82da

File tree

5 files changed

+96
-10
lines changed

5 files changed

+96
-10
lines changed

include/boost/math/special_functions/beta.hpp

+18-8
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,10 @@ T ibeta_power_terms(T a,
230230
T agh = static_cast<T>(a + Lanczos::g() - 0.5f);
231231
T bgh = static_cast<T>(b + Lanczos::g() - 0.5f);
232232
T cgh = static_cast<T>(c + Lanczos::g() - 0.5f);
233-
result = Lanczos::lanczos_sum_expG_scaled(c) / (Lanczos::lanczos_sum_expG_scaled(a) * Lanczos::lanczos_sum_expG_scaled(b));
233+
if ((a < tools::min_value<T>()) || (b < tools::min_value<T>()))
234+
result = 0; // denominator overflows in this case
235+
else
236+
result = Lanczos::lanczos_sum_expG_scaled(c) / (Lanczos::lanczos_sum_expG_scaled(a) * Lanczos::lanczos_sum_expG_scaled(b));
234237
result *= prefix;
235238
// combine with the leftover terms from the Lanczos approximation:
236239
result *= sqrt(bgh / boost::math::constants::e<T>());
@@ -389,14 +392,15 @@ T ibeta_power_terms(T a,
389392
}
390393
else
391394
{
392-
T p1 = pow(b1, a / b);
395+
// This protects against spurious overflow in a/b:
396+
T p1 = (b1 < 1) && (b < 1) && (tools::max_value<T>() * b < a) ? static_cast<T>(0) : static_cast<T>(pow(b1, a / b));
393397
T l3 = (p1 != 0) && (b2 != 0) ? (log(p1) + log(b2)) * b : tools::max_value<T>(); // arbitrary large value if the logs would fail!
394398
if((l3 < tools::log_max_value<T>())
395399
&& (l3 > tools::log_min_value<T>()))
396400
{
397401
result *= pow(p1 * b2, b);
398402
}
399-
else
403+
else if(result != 0) // we can elude the calculation below if we're already going to be zero
400404
{
401405
l2 += l1 + log(result);
402406
if(l2 >= tools::log_max_value<T>())
@@ -656,7 +660,10 @@ T ibeta_series(T a, T b, T x, T s0, const Lanczos&, bool normalised, T* p_deriva
656660
T agh = static_cast<T>(a + Lanczos::g() - 0.5f);
657661
T bgh = static_cast<T>(b + Lanczos::g() - 0.5f);
658662
T cgh = static_cast<T>(c + Lanczos::g() - 0.5f);
659-
result = Lanczos::lanczos_sum_expG_scaled(c) / (Lanczos::lanczos_sum_expG_scaled(a) * Lanczos::lanczos_sum_expG_scaled(b));
663+
if ((a < tools::min_value<T>()) || (b < tools::min_value<T>()))
664+
result = 0; // denorms cause overflow in the Lanzos series, result will be zero anyway
665+
else
666+
result = Lanczos::lanczos_sum_expG_scaled(c) / (Lanczos::lanczos_sum_expG_scaled(a) * Lanczos::lanczos_sum_expG_scaled(b));
660667

661668
if (!(boost::math::isfinite)(result))
662669
result = 0;
@@ -689,10 +696,13 @@ T ibeta_series(T a, T b, T x, T s0, const Lanczos&, bool normalised, T* p_deriva
689696
//
690697
// Oh dear, we need logs, and this *will* cancel:
691698
//
692-
result = log(result) + l1 + l2 + (log(agh) - 1) / 2;
693-
if(p_derivative)
694-
*p_derivative = exp(result + b * log(y));
695-
result = exp(result);
699+
if (result != 0) // elude calculation when result will be zero.
700+
{
701+
result = log(result) + l1 + l2 + (log(agh) - 1) / 2;
702+
if (p_derivative)
703+
*p_derivative = exp(result + b * log(y));
704+
result = exp(result);
705+
}
696706
}
697707
}
698708
else

include/boost/math/special_functions/detail/ibeta_inverse.hpp

+15-1
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,21 @@ T ibeta_inv_imp(T a, T b, T p, T q, const Policy& pol, T* py)
825825
std::swap(p, q);
826826
invert = !invert;
827827
}
828-
if(pow(p, 1/a) < 0.5)
828+
if (a < tools::min_value<T>())
829+
{
830+
// Avoid spurious overflows for denorms:
831+
if (p < 1)
832+
{
833+
x = 1;
834+
y = 0;
835+
}
836+
else
837+
{
838+
x = 0;
839+
y = 1;
840+
}
841+
}
842+
else if(pow(p, 1/a) < 0.5)
829843
{
830844
#ifndef BOOST_NO_EXCEPTIONS
831845
try

include/boost/math/tools/roots.hpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,8 @@ namespace detail {
589589
#ifdef BOOST_MATH_INSTRUMENT
590590
std::cout << "Second order root iteration, delta = " << delta << ", residual = " << f0 << "\n";
591591
#endif
592-
T convergence = fabs(delta / delta2);
592+
// We need to avoid delta/delta2 overflowing here:
593+
T convergence = (fabs(delta2) > 1) || (fabs(tools::max_value<T>() * delta2) > fabs(delta)) ? fabs(delta / delta2) : tools::max_value<T>();
593594
if ((convergence > 0.8) && (convergence < 2))
594595
{
595596
// last two steps haven't converged.

test/Jamfile.v2

+1
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ test-suite special_fun :
169169
[ run git_issue_810.cpp ../../test/build//boost_unit_test_framework ]
170170
[ run git_issue_826.cpp ../../test/build//boost_unit_test_framework ]
171171
[ run git_issue_961.cpp ]
172+
[ run git_issue_1006.cpp ]
172173
[ run special_functions_test.cpp ../../test/build//boost_unit_test_framework ]
173174
[ run test_airy.cpp test_instances//test_instances pch_light ../../test/build//boost_unit_test_framework ]
174175
[ run test_bessel_j.cpp test_instances//test_instances pch_light ../../test/build//boost_unit_test_framework ]

test/git_issue_1006.cpp

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// (C) Copyright Matt Borland 2023.
2+
// Use, modification and distribution are subject to the
3+
// Boost Software License, Version 1.0. (See accompanying file
4+
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5+
6+
#include "math_unit_test.hpp"
7+
#include <cfenv>
8+
#include <iostream>
9+
#include <boost/math/distributions/beta.hpp>
10+
11+
#pragma STDC FENV_ACCESS ON
12+
13+
// Show and then clear the fenv flags
14+
void show_fpexcept_flags()
15+
{
16+
bool fe = false;
17+
18+
if (std::fetestexcept(FE_OVERFLOW))
19+
{
20+
fe = true;
21+
std::cerr << "FE_OVERFLOW" << std::endl;
22+
}
23+
if (std::fetestexcept(FE_UNDERFLOW))
24+
{
25+
//fe = true;
26+
std::cerr << "FE_UNDERFLOW" << std::endl;
27+
}
28+
if (std::fetestexcept(FE_DIVBYZERO))
29+
{
30+
fe = true;
31+
std::cerr << "FE_DIVBYZERO" << std::endl;
32+
}
33+
if (std::fetestexcept(FE_INVALID))
34+
{
35+
fe = true;
36+
std::cerr << "FE_INVALID" << std::endl;
37+
}
38+
39+
CHECK_EQUAL(fe, false);
40+
41+
std::feclearexcept(FE_ALL_EXCEPT);
42+
}
43+
44+
int main()
45+
{
46+
// Default Scipy policy
47+
using my_policy = boost::math::policies::policy<boost::math::policies::promote_float<false>, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy>;
48+
49+
double a = 1e-307;
50+
51+
while (a)
52+
{
53+
const auto dist_a = boost::math::beta_distribution<double, my_policy>(1e-308, 5.0);
54+
const auto dist_a_ppf = boost::math::quantile(dist_a, 0.2);
55+
show_fpexcept_flags();
56+
CHECK_MOLLIFIED_CLOSE(dist_a_ppf, 0.0, 1e-10);
57+
a /= 2;
58+
}
59+
return boost::math::test::report_errors();
60+
}

0 commit comments

Comments
 (0)