Repository navigation
Conversation
This was referenced Oct 8, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2031. Fixes #2923. Fixes #3234. Fixes #3377. Fixes other functions. Partially addresses #2790.
Currently this PR is based on stable-inc-beta branch PR #3409, so that PR should be merged first.
This improves beta_lccdf, beta_proportion_lccdf, binomial_lccdf, neg_binomial_lccdf, neg_binomial_2_lccdf, student_t_cdf, student_t_lcdf, student_t_lccdf
This PR was Claude assisted
EDIT 2026-10-08: Added three more commits to fix more issues in functions touched by this PR
Summary
Seven tail functions form the complement of the regularized incomplete beta as
1 - inc_beta(a, b, x)in linear space:beta_lccdf,beta_proportion_lccdf,neg_binomial_lccdf,binomial_lccdf, and theq >= 2branch ofstudent_t_cdf,student_t_lcdfandstudent_t_lccdf. Onceinc_betarounds to 1 the complement is exactly 0, its log is-inf, and the gradients areinforNaN. That happens as soon as the tail probability is beloweps, which is the regime thelccdffunctions exist to represent. Theq < 2branch of the three Student t functions has a second subtraction of the same kind,1 - r.This PR evaluates each complement without the subtraction, from whichever of
xand1 - xis at most 1/2, through a new internal functioninternal::inc_beta_complement(inbinomial_lccdfdirectly by the symmetry relation, which there needs no helper), and takes the shape gradients fromgrad_reg_inc_betaon the same argument. It replaces the density factors, whose powers underflow separately and gave0 / 0, withinc_beta_ddz; inneg_binomial_lccdfthat density is also evaluated at the argument at most 1/2. It forms1 - rin the Student tq < 2branch without subtraction.Which part of the PR fixes which issue:
beta_lccdf(0.865169 | 1, 19)is-infbeta_lccdfstudent_t_cdfp-values of exactly 0q >= 2branchstudent_t_cdfis 0 or 1 for large|t|1 - rin the Student tq < 2branchneg_binomial_lccdfis-inffor largebeta1 / (beta + 1), and the density factor of thebetagradientDBL_MIN; below thatlog_inc_betais neededDepends on #3409: the shape gradients go through the repaired
grad_reg_inc_beta. The value changes are independent of it. This PR is based on thestable-inc-betabranch of #3409, so its diff shows only its own seven commits; it will be retargeted todevelopwhen #3409 merges.Fixes #3377. The reporter's case,
beta_lccdf(0.865169 | 1, 19), returns-infwith infinite gradients ondevelop; on this branch it returns-38.0709295957162, equal to the closed form19 * log1m(y)to all printed digits, and the three gradients match a 50-digit reference (d/dy = -140.917148134 = -19 / (1 - y),d/dalpha = 3.4106443951,d/dbeta = -2.00373313662). The cause is the one described below:(1 - y)^19 = 3e-17is beloweps, so1 - inc_beta(...)is exactly 0.Fixes #3234.
student_t_cdfreturned exactly 0 for p-values below about 1e-16 wheneverq = df / t^2 >= 2, the branch that formed1 - inc_beta(...). At the six rows of that report this branch matches a 40-digit reference to all printed digits; for example2 * student_t_cdf(-9.466995 | 196.9309, 0, 1)is9.173253e-18, wheredevelopgives0.Fixes #2923. In the
q < 2branch,r = 1 / (1 + q)rounds to 1 onceqis beloweps, so1 - ris 0 and the tail probability is 0. The reported casestudent_t_cdf(-1e8 | 0.1, 0, 1)is 0 ondevelopfor a true 0.0661503218, and its mirror at1e8is 1 for a true 0.9338496782. Before that,1 - rloseseps / qrelative: 1e-13 at|t| = 10withnu = 0.1. Anynufails once|t|exceeds about6.7e7 sqrt(nu); the Cauchystudent_t_lcdf(-1e10 | 1, 0, 1)is-inffor a true -24.17. Over the reported table (nu = 0.1,|t|from 1 to 1e29, both signs) this branch is within 4e-16 in the value and 5e-15 in they,nuandsigmagradients of a 60-digit reference.Fixes #2031. The reported case
neg_binomial_lccdf(0 | 1, 1e18)is-infondevelopfor a true -41.4465316739, which islog1m_exp(-alpha * log1p(1 / beta)). On this branch the value and both gradients are correct to 1e-15. Forn >= 1thebetagradient also needs the density factor at1 / (beta + 1): atp = beta / (beta + 1)it is exactly 0 onceprounds to 1 (frombetanear 1e16), where the true gradient is about-(n + 1) / beta, and it loses 8 digits atbeta = 1e8.beta_lccdf(0.9 | 50, 50)-inf, gradients±infbeta_lccdf(0.758 | 352, 590)-inf, gradients±infbeta_proportion_lccdf(0.758 | 0.4, 900)-inf, gradients±infneg_binomial_lccdf(50 | 2, 99)-inf, gradientsNaNneg_binomial_lccdf(1 | 2, 1e18)-inf, gradientsinfbinomial_lccdf(500 | 1000, 0.2)-inf, gradientNaNstudent_t_lccdf(10 | 1000, 0, 1)-inf, gradients∓infstudent_t_lcdf(-10 | 1000, 0, 1)-inf, gradients∓infstudent_t_cdf(-10 | 1000, 0, 1)NaNstudent_t_cdf(-1e8 | 0.1, 0, 1)NaNstudent_t_lcdf(-1e10 | 1, 0, 1)-inf, gradientsinfThe defect on
developEverything in this section describes
develop. The reference is mpmath at 50 or 60 digits, each value by two independent routes; for the beta family the complement is computed on the small side of the symmetry relation, and for Student t the tail is0.5 I_{nu / (nu + t^2)}(nu / 2, 1 / 2), differentiated numerically. The numbers were confirmed by compiling and running the real headers.1. The complement cancels.
Pn = 1.0 - inc_beta(alpha, beta, y)is 0 wheneverinc_betareturns exactly 1, which is whenever the true complement is beloweps = 2.2e-16, that is whenever the truelccdfis belowlog(eps) = -36.7.beta_lccdf(0.9 | 50, 50), both shapes 50 andyat 0.9, returns-inffor a true-54.1. A Student t with 1000 degrees of freedom at 10 standard deviations returns-inffor a true-50.8. None of the parameter sets in the table above is unusual; the only condition is a tail probability below1e-16.2. The gradients follow.
inv_Pnisinf, so the shape andypartials areinfwhere the density factor is finite, andNaNwhere it is not.student_t_cdf(-10 | 1000, 0, 1)is the worst case: its value8.3e-23is representable and correct, so nothing looks wrong, while both gradients areNaN.3. The density factor underflows on its own.
pow(theta, n) * pow(1 - theta, N - n - 1) / beta(N - n, n + 1)inbinomial_lccdf, and the equivalent in the other files, is a ratio of quantities that each underflow long before the ratio does.binomial_lccdf(500 | 1000, 0.2)has a correct value after the complement is repaired, and still aNaNgradient:pow(0.2, 500)is 0 andbeta(500, 501)is 0. Inneg_binomial_lccdfthe factorpow(1 - p, n)is formed fromp = beta / (beta + 1), which rounds to 1 for largebeta.4.
1 - rin the Student tq < 2branch. The branch forms1 - rby subtraction, withr = 1 / (1 + q)andq = nu / t^2. See #2923 above.Two regimes must be kept apart. Where
inc_betaunderflows to 0 the complement is 1 and the old code is right:neg_binomial_lccdf(3 | 900, 0.02)returns 0 with zero gradients on both trees, and that is the correct answer.What this PR does
The complement from the argument at most 1/2. Boost's
ibetaforms1 - xfrom its argument, so an argument close to 1 loses relative precision in1 - x. The symmetry relation1 - I_x(a, b) = I_{1-x}(b, a)removes the cancellation of a tail beloweps, but it passes1 - x, which is close to 1 wherexis small. The newinternal::inc_beta_complement(a, b, x, one_m_x)inprim/fun/inc_beta.hpptakes bothxand1 - xand calls Boost with the one that is at most 1/2:x > 1/2:inc_beta(b, a, 1 - x), by the symmetry relation;xbelow the meana / (a + b), whileinc_beta(a, b, x)is at most 1/2:1 - inc_beta(a, b, x), which then has full precision;ibetac(a, b, x).The middle case is needed because Boost's
ibetacfor the arcsine casea = b = 1/2(the Cauchy,nu = 1) evaluatesasin(sqrt(1 - x)), which loses digits for smallx. For autodiffT_partials_return, which occurs only in second-order autodiff, the function uses the symmetry relation throughinc_beta.The shape gradients come from
grad_reg_inc_betaon the same argument: forx <= 1/2the call on(a, b, x), as ondevelop, whose outputs are the negatives of the derivatives of the complement; forx > 1/2the call on(b, a, 1 - x), whose two outputs are the derivatives of the complement itself with respect tobanda.grad_reg_inc_betaapplies the symmetry relation internally above the mean, so on an argument at most 1/2 it keeps its relative precision in the deep tail too.beta_lccdf:x = y,1 - x = 1 - y.beta_proportion_lccdf: the same witha = mu kappaandb = kappa - mu kappa; the chain rule tomuandkappais unchanged.neg_binomial_lccdf:x = p = beta / (beta + 1)and1 - x = 1 / (beta + 1), which the function already has asinv_beta_p1; both are formed without cancellation.binomial_lccdf: the complement isinc_beta(n + 1, N - n, theta); Boost forms1 - thetaexactly fromtheta, so the function needs no helper. The derivative is closed form.neg_binomial_2_lccdfis not changed itself: it returnsneg_binomial_lccdf(n, phi, phi / mu), so it gets every change ofneg_binomial_lccdf.student_t_cdf,student_t_lcdf,student_t_lccdf: theq >= 2branch takesz = 1 - I_r(1/2, nu/2)from the helper withr <= 1/3; its gradient code is the same as ondevelop. Theq < 2branch forms1 - ras1 / (1 + t^2 / nu), which has no cancellation and is 1 att = 0. The two branches compute the same quantity; continuity acrossq = 2is tested.The density factor.
beta_lccdf,beta_proportion_lccdf,neg_binomial_lccdfandbinomial_lccdfuseinc_beta_ddzfor the density instead of the product of powers over a beta function. It is the same quantity; fordoubleit is Boost'sibeta_derivative, which scales internally.beta_cdfalready used it, so this also removes a duplicated expression.ibeta_derivativealso forms1 - xfrom its argument, and returns 0 atx == 1when its second shape is above 1; inneg_binomial_lccdfthe density atpis therefore evaluated as the equal reflected density at1 - pwhenp > 1/2. The three Student t files keep theird_ibeta, which does not underflow in their parameter range.The bound
y = 1.beta_lccdfandbeta_proportion_lccdfreturnnegative_infinity()aty >= 1before the loop, the same explicit bound handling thatneg_binomial_lccdfandbinomial_lccdfalready have (see Testing).After the change the
-infthreshold moves fromepstoDBL_MIN, that is from a tail probability ofe^-36.7toe^-709.Accuracy after the change
Every value and every gradient in the table above matches the reference to all printed digits. The residual relative errors are 1e-15 to 1e-10, which is the accuracy of Boost's
inc_betaand of the gradient root; nothing in this PR adds error of its own.The change keeps the accuracy of
developwheredevelopwas correct. At 117 points, in the bulk (|t|down to 1e-7,ydown to 1e-10,betadown to 1e-9) and in the tails, compared point by point, no value or gradient is more than 4 times less accurate than ondevelopabove a relative error of 1e-14, at every point wheredevelopis finite. For example thenugradient ofstudent_t_cdf(-1e-7 | 1, 0, 1)is within 1e-15, and thealphagradient ofbeta_lccdf(1e-10 | 0.5, 3)within 6e-16.Continuity across the Student t branch seam: at
y = 1.9999, 2.0, 2.0001withnu = 8, the value and thenugradient are linear inyto 1e-8 on both sides ofq = 2.Testing
Two new files, with fixed references from the high-precision computation. Finite differences cannot see these defects: at the failing points the finite difference of
-infisNaN, andexpect_adwould report agreement.rev/prob/beta_ccdf_log_test.cpp, 8 cases:beta_lccdfat three points belowepsand one control, and at four small-ypoints (two in the bulk, two with the complement beloweps);beta_proportion_lccdfat one deep-tail point and two small-ypoints;neg_binomial_lccdfat two deep-tail points, six large-betapoints (Numerical issues in neg_binomial_lccdf if beta is very large #2031: four withbetafrom 1e8 to 1e18, one control on each side ofbeta = 1) and four points withbeta < 1;binomial_lccdfwith its gradient.rev/prob/student_t_ccdf_log_test.cpp, 6 cases:student_t_lccdfat five points in both branches; fourstudent_t_lccdfpoints at larget(Student T CDF precision for large values #2923); the reported Student T CDF precision for large values #2923 case, its mirror and the Cauchylcdfat-1e10, with the value and all three gradients; three points neart = 0with the value and all three gradients (nu = 1,nu = 30); the branch-seam continuity check;student_t_lcdfandstudent_t_cdfat the point where the cdf's gradients wereNaN.Regression swap: with
develop's versions of the changed headers installed under these two files, 11 of the 14 cases fail. The three that pass are thebinomial_lccdfcase (its header was not swapped in this run), the seam-continuity check, and the near-t = 0Student t case, wheredevelopis accurate. With this branch's headers all 14 pass, before and after the swap.Full suites, all passing on the final head: the root
inc_betaunit tests inprim,rev,fwdandmix; the unit prob tests ofbeta,beta_proportion,student_tandneg_binomial*(which includesneg_binomial_2); and thetest/probgenerated tests ofbeta,beta_proportion,student_t,neg_binomial,neg_binomial_2andbinomial.The generated tests found one thing the fixed-reference tests did not.
inc_beta_ddzfordoubleis Boost'sibeta_derivative, which throws on overflow where the oldpowproduct returnedinf; aty = 1with a second shape below 1 the density is infinite, and the generatedUpperBoundcase with an autodiffyreaches it. This is the reason for the explicit bound aty = 1. The value at the bound is unchanged,-inf; they-partial there is 0 instead ofinforNaN.Known limits, not addressed here
DBL_MINthe complement is not representable at all.neg_binomial_lccdf(200 | 2, 99)andbinomial_lccdf(500 | 1000, 0.01)are still-inf, for true values -920.34 and -1622.73. That needslog_inc_beta, the incomplete beta in log space, which the tree does not have. The same function would repair the mirror defect inbeta_lcdfand its siblings, whereinc_betaunderflows to 0 foryfar below the mean. It is a separate PR: a new function with its own overloads and tests.t^2overflows above|t| = 1.3e154; that range would need a log form.lccdfvalue that is the log of a probability that rounds to 1 is returned as 0, as ondevelop:beta_proportion_lccdf(1e-8 | 0.3, 10)is 0 for a true -8.4e-23. The gradients there are correct.beta_cdf,neg_binomial_cdfandneg_binomial_2_cdfare not affected; they return the probability itself, where 0 is the correct rounding.Side Effects
No
Release notes
More stable computation in beta_lccdf, beta_proportion_lccdf, binomial_lccdf, neg_binomial_lccdf, neg_binomial_2_lccdf, student_t_cdf, student_t_lcdf, student_t_lccdf
Checklist
Copyright holder: Aalto University
The copyright holder is typically you or your assignee, such as a university or company. By submitting this pull request, the copyright holder is agreeing to the license the submitted work under the following licenses:
- Code: BSD 3-clause (https://opensource.org/licenses/BSD-3-Clause)
- Documentation: CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
the basic tests are passing
./runTests.py test/unit)make test-headers)make test-math-dependencies)make doxygen)make cpplint)the code is written in idiomatic C++ and changes are documented in the doxygen
the new changes are tested