Skip to content

Commit ce18ea4

Browse files
authored
Merge pull request #1025 from boostorg/pr1007
Improve ibeta power term calculation in the Sterling case
2 parents 0d941f0 + 469bff7 commit ce18ea4

File tree

3 files changed

+97
-12
lines changed

3 files changed

+97
-12
lines changed

include/boost/math/distributions/detail/inv_discrete_quantile.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ typename Dist::value_type
147147
// we're assuming that "guess" is likely to be accurate
148148
// to the nearest int or so:
149149
//
150-
else if(adder != 0)
150+
else if((adder != 0) && (a + adder != a))
151151
{
152152
//
153153
// If we're looking for a large result, then bump "adder" up

include/boost/math/special_functions/beta.hpp

Lines changed: 95 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -473,20 +473,108 @@ T ibeta_power_terms(T a,
473473
if ((shift_a == 0) && (shift_b == 0))
474474
{
475475
T power1, power2;
476+
bool need_logs = false;
476477
if (a < b)
477478
{
478-
power1 = pow((x * y * c * c) / (a * b), a);
479-
power2 = pow((y * c) / b, b - a);
479+
BOOST_IF_CONSTEXPR(std::numeric_limits<T>::has_infinity)
480+
{
481+
power1 = pow((x * y * c * c) / (a * b), a);
482+
power2 = pow((y * c) / b, b - a);
483+
}
484+
else
485+
{
486+
// We calculate these logs purely so we can check for overflow in the power functions
487+
T l1 = log((x * y * c * c) / (a * b));
488+
T l2 = log((y * c) / b);
489+
if ((l1 * a > tools::log_min_value<T>()) && (l1 * a < tools::log_max_value<T>()) && (l2 * (b - a) < tools::log_max_value<T>()) && (l2 * (b - a) > tools::log_min_value<T>()))
490+
{
491+
power1 = pow((x * y * c * c) / (a * b), a);
492+
power2 = pow((y * c) / b, b - a);
493+
}
494+
else
495+
{
496+
need_logs = true;
497+
}
498+
}
480499
}
481500
else
482501
{
483-
power1 = pow((x * y * c * c) / (a * b), b);
484-
power2 = pow((x * c) / a, a - b);
502+
BOOST_IF_CONSTEXPR(std::numeric_limits<T>::has_infinity)
503+
{
504+
power1 = pow((x * y * c * c) / (a * b), b);
505+
power2 = pow((x * c) / a, a - b);
506+
}
507+
else
508+
{
509+
// We calculate these logs purely so we can check for overflow in the power functions
510+
T l1 = log((x * y * c * c) / (a * b)) * b;
511+
T l2 = log((x * c) / a) * (a - b);
512+
if ((l1 * a > tools::log_min_value<T>()) && (l1 * a < tools::log_max_value<T>()) && (l2 * (b - a) < tools::log_max_value<T>()) && (l2 * (b - a) > tools::log_min_value<T>()))
513+
{
514+
power1 = pow((x * y * c * c) / (a * b), b);
515+
power2 = pow((x * c) / a, a - b);
516+
}
517+
else
518+
need_logs = true;
519+
}
485520
}
486-
if (!(boost::math::isnormal)(power1) || !(boost::math::isnormal)(power2))
521+
BOOST_IF_CONSTEXPR(std::numeric_limits<T>::has_infinity)
487522
{
488-
// We have to use logs :(
489-
return prefix * exp(a * log(x * c / a) + b * log(y * c / b)) * scaled_tgamma_no_lanczos(c, pol) / (scaled_tgamma_no_lanczos(a, pol) * scaled_tgamma_no_lanczos(b, pol));
523+
if (!(boost::math::isnormal)(power1) || !(boost::math::isnormal)(power2))
524+
{
525+
need_logs = true;
526+
}
527+
}
528+
if (need_logs)
529+
{
530+
//
531+
// We want:
532+
//
533+
// (xc / a)^a (yc / b)^b
534+
//
535+
// But we know that one or other term will over / underflow and combining the logs will be next to useless as that will cause significant cancellation.
536+
// If we assume b > a and express z ^ b as(z ^ b / a) ^ a with z = (yc / b) then we can move one power term inside the other :
537+
//
538+
// ((xc / a) * (yc / b)^(b / a))^a
539+
//
540+
// However, we're not quite there yet, as the term being exponentiated is quite likely to be close to unity, so let:
541+
//
542+
// xc / a = 1 + (xb - ya) / a
543+
//
544+
// analogously let :
545+
//
546+
// 1 + p = (yc / b) ^ (b / a) = 1 + expm1((b / a) * log1p((ya - xb) / b))
547+
//
548+
// so putting the two together we have :
549+
//
550+
// exp(a * log1p((xb - ya) / a + p + p(xb - ya) / a))
551+
//
552+
// Analogously, when a > b we can just swap all the terms around.
553+
//
554+
// Finally, there are a few cases (x or y is unity) when the above logic can't be used
555+
// or where there is no logarithmic cancellation and accuracy is better just using
556+
// the regular formula:
557+
//
558+
T xc_a = x * c / a;
559+
T yc_b = y * c / b;
560+
if ((x == 1) || (y == 1) || (fabs(xc_a - 1) > 0.25) || (fabs(yc_b - 1) > 0.25))
561+
{
562+
// The above logic fails, the result is almost certainly zero:
563+
power1 = exp(log(xc_a) * a + log(yc_b) * b);
564+
power2 = 1;
565+
}
566+
else if (b > a)
567+
{
568+
T p = boost::math::expm1((b / a) * boost::math::log1p((y * a - x * b) / b));
569+
power1 = exp(a * log1p((x * b - y * a) / a + p * (x * c / a)));
570+
power2 = 1;
571+
}
572+
else
573+
{
574+
T p = boost::math::expm1((a / b) * boost::math::log1p((x * b - y * a) / a));
575+
power1 = exp(b * log1p((y * a - x * b) / b + p * (y * c / b)));
576+
power2 = 1;
577+
}
490578
}
491579
return prefix * power1 * power2 * scaled_tgamma_no_lanczos(c, pol) / (scaled_tgamma_no_lanczos(a, pol) * scaled_tgamma_no_lanczos(b, pol));
492580
}

test/test_binomial.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -716,10 +716,7 @@ void test_spots(RealType T)
716716

717717
check_out_of_range<boost::math::binomial_distribution<RealType> >(1, 1); // (All) valid constructor parameter values.
718718

719-
// TODO: Generic ibeta_power_terms has accuracy issue when long
720-
// double is not precise enough, causing overflow in this case.
721-
if(!std::is_same<RealType, real_concept>::value ||
722-
sizeof(boost::math::concepts::real_concept_base_type) > 8)
719+
// See bug reported here: https://github.com/boostorg/math/pull/1007
723720
{
724721
using namespace boost::math::policies;
725722
typedef policy<discrete_quantile<integer_round_outwards> > Policy;

0 commit comments

Comments
 (0)