Correct the scale of the continued fraction's convergence test - #553
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## fix/expint-same-precision #553 +/- ##
==========================================================
Coverage 94.68% 94.68%
==========================================================
Files 14 14
Lines 3010 3010
==========================================================
Hits 2850 2850
Misses 160 160
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
andreasnoack
force-pushed
the
fix/expint-same-precision
branch
from
August 23, 2026 18:32
523a092 to
ef23f33
Compare
`En_cf_nogamma` compared `abs(Aprev*B - A*Bprev)` against `10*eps(real(B))*abs(B*Bprev)`, which is wrong on three counts. `eps(real(B))` is `eps` of the value `z + ν` rather than of the type, so the tolerance grew with `|z|`: 7.1e-15 at `|z| = 40` instead of 2.2e-16. And dividing the comparison through by `|B*Bprev|` shows it to be `|Aprev/Bprev - A/B| < ϵ`, an absolute test on a convergent of size `|e^z E_ν(z)| ≈ 1/|z|`. Together these give an effective relative tolerance of about `10*eps(one(T))*|z|²`, an error floor that grows with the argument: 2.8e-13 by `z = 32` for `ν = 2` on the positive real axis, against 1.8e-15 now. `B*Bprev` is also the largest of the three products, since `|B| ≈ |z||A|`, so it reaches `floatmax` while the numerator's mixed `A·B` products are still finite, and the comparison then succeeds against `Inf`. That is #545: every `Float32` seed point the `real(z) < 0` branch picks for the cases reported there exits that way, after 20 to 221 iterations, with relative errors from 0.0179 to 0.874. The rescaling below the test cannot prevent it, having fired at most once by then. The test now uses `eps(T)` and `abs(A*Bprev)`, so the threshold is the size of the terms it is compared against, and the rescaling moves above it. The placement matters: the bound has to hold when the test consumes the products, not one iteration earlier, and for `Float16` `floatmax^(1/4)` is only 16, so a single iteration's growth can overflow them. The rescaling also has to consider `B` as well as `A`, since near the negative real axis the two sequences decouple and `A` leads by up to 112x, so testing `A` alone understates the maximum by a factor of `|z|`. `sqrt(floatmax(T))` becomes `sqrt(floatmax(T))/4` to leave margin for the overshoot between checks, there being two recurrence steps per loop iteration. Over a grid of ±60 × ±30 for `ν = 1, 2, 2.5, 1+i` the worst relative error drops from 2.0e-12 to 4.3e-15 and the mean from 8.0e-14 to 3.7e-16, in both half planes, and the `Float32` and `Float16` cases of #545 return to the resolution of their types. The cost per iteration is unchanged; the roughly 15 percent more iterations are the ones the old test skipped. Fixes #545.
Nine points with `abs2(z) > 9`, checked against MPFR's incomplete gamma at `rtol = 50*eps(Float64)`. All nine fail before the previous commit, by 74 to 1313 ulps, and pass after it, within 8.
andreasnoack
force-pushed
the
fix/expint-cf-criterion
branch
from
August 23, 2026 19:22
55b92e4 to
b10bc46
Compare
stevengj
approved these changes
Aug 25, 2026
Member
|
LGTM. |
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.
Stacked on #550.
En_cf_nogamma's convergence test comparedabs(Aprev*B - A*Bprev)against10*eps(real(B))*abs(B*Bprev). Three things are wrong with that:eps(real(B))isepsof the valuez + ν, not of the type, so the tolerance grows with|z|— 7.1e-15 at|z| = 40.|B*Bprev|shows the test to be|Aprev/Bprev - A/B| < ϵ, i.e. absolute, on a convergent of size|e^z E_ν(z)| ≈ 1/|z|. With the first point that gives an effective relative tolerance of about10*eps(one(T))*|z|².B*Bprevis the largest of the three products (|B| ≈ |z||A|), so it reachesfloatmaxwhile the numerator's mixedA·Bproducts are still finite, andfinite < Infthen succeeds unconditionally.The last one is #545. Running the original loop on the
Float32seed points thereal(z) < 0branch picks for the reported cases, every one exits on an overflowed threshold:The fix uses
eps(T)andabs(A*Bprev), moves the rescaling above the test, tests both|A|and|B|, and lowers the threshold tosqrt(floatmax(T))/4. Each of those is load-bearing: the bound has to hold when the test consumes the products rather than one iteration earlier (forFloat16,floatmax^(1/4)is 16, so one iteration's growth can overflow them);AovertakesBby up to 112x near the cut, so checkingAalone understates the maximum by|z|; and the margin covers the overshoot between checks, there being two recurrence steps per loop iteration.Effect
Against
Complex{BigFloat}references at 200 bits, grid ±60 × ±30:Both half-planes improve by comparable factors — the right half-plane never touches the
real(z) < 0walk, so the fix stands on its own there.Narrow types, which is what #545 reports:
Float32)Float32grid, ν = 2.5, 844 pointsFloat16grid, ν = 2, 458 pointsCost: unchanged per iteration (3.50 → 3.88 ns real, 16.7 → 17.0 ns complex, for
En_cfat ν = 2.5); the ~15% more iterations are the ones the old test skipped.Tests
A new
expint continued fractiontestset covers nine points withabs2(z) > 9, for ν = 2, 2.5, 3, 5 and 10, against MPFR's incomplete gamma atrtol = 50*eps(Float64). All nine fail on the base branch, by 74 to 1313 ulps, and pass here, within 8.Full suite passes:
expint6373 pass, 1 pre-existing@test_broken.Not fixed here
#453 improves about 5x (worst 2.75e-13 at z = 31.8 → 5.54e-14 at z = 2.89) but stays open: its residual is the cancellation in the series about the origin below
|z| = 3, which is #551's mechanism. #546 is untouched. Details and measurements in #453 (comment).🤖 Generated with Claude Code