expint is inaccurate for Float32/Float16 arguments near the negative real axis
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 55/100
Research direction
Start by reading the real(z) < 0 branch of _expint in src/expint.jl, especially the imstart, nsteps, and Taylor-walk logic. Then inspect test/expint.jl and reproduce the reported Float32 and Float16 cases against Float64 or BigFloat references. Done means low-precision results near the negative real axis are accurate while the existing Float64 behavior remains correct.
Written by the indexing model from the issue text.
Description
expint(ν, z) loses most of its accuracy for Float32 and Float16 arguments when z is on or close to the negative real axis. The error is far above the resolution of the type: up to ~85% relative error, i.e. the result is simply wrong rather than merely rounded.
Tested with SpecialFunctions v2.8.3 on Julia 1.12.7.
julia> using SpecialFunctions
julia> expint(2, ComplexF32(-3.5))
-2.9899444580078125 - 10.99557399108051im # ← wrong
julia> expint(2, ComplexF64(-3.5))
-15.623287024340843 - 10.995574287564276im
julia> setprecision(BigFloat, 128) do; expint(2, Complex{BigFloat}(-7//2, 0)); end
-15.62328702434085976994732524631780778557 - 10.99557428756427633461925184147826009474im
so the Float64 result is correct and the Float32 one is off by a factor of 5 in the real part. Float16 is likewise wrong (-3.19140625 - 10.994015112188281im).
A few more points, Float32 versus Float64 input:
| ν | z | Float32 input |
Float64 input |
rel. error |
|---|---|---|---|---|
1 |
-3.5f0 + 0.0f0im |
-11.107379f0 - 3.1415927f0im |
-13.92535399515229 - 3.141592653589793im |
0.197 |
2 |
-3.5f0 + 0.0f0im |
-2.9899445f0 - 10.995574f0im |
-15.623287024340843 - 10.995574287564276im |
0.661 |
2 |
-3.5f0 + 0.01f0im |
-3.0192275f0 - 0.69151247f0im |
-15.654229875324825 - 10.856321873984799im |
0.851 |
3 |
-3.5f0 + 0.0f0im |
-8.832054f0 - 19.242254f0im |
-10.783026313250343 - 19.24225500323748im |
0.0884 |
2 |
-4.0f0 + 0.0f0im |
-25.642519f0 - 12.566371f0im |
-23.925347847080612 - 12.566370614359172im |
0.0635 |
2 |
-10.0f0 + 0.0f0im |
-2854.4534f0 - 31.415928f0im |
-2895.823967611959 - 31.41592653589794im |
0.0143 |
1 |
-5.0f0 + 0.0f0im |
-39.963936f0 - 3.1415927f0im |
-40.18527535580315 - 3.141592653589793im |
0.00549 |
1.5 |
-10.0f0 + 0.0f0im |
-2653.0286f0 - 11.209983f0im |
-2676.2869535640534 - 11.209982432795858im |
0.00869 |
The order ν can be an Integer or a float of the same type as z; it makes no difference. Points with a positive real part are fine (relative error ~1e-7 for Float32), and Float64 and BigFloat are fine everywhere I looked, so this is specific to the low-precision types on the negative real axis.
Where it seems to come from
The real(z) < 0 branch of _expint (the Amos procedure in src/expint.jl) walks in from a point with a larger imaginary part:
imstart = (imz == 0) ? abs(z)*sqrt(eps(typeof(real(z)))) : imz
z₀ = rez + imstart*im
...
nsteps = ceil(2 * (imstart - imz))
Δ = (imz - imstart)*im / nsteps
for j = 1:nsteps
E_start = En_taylor(ν, E_start, z₀, Δ)
z₀ += Δ
end
Several parts of this are tied to the precision of the argument in a way that does not hold up for Float32/Float16:
imstartisabs(z)*sqrt(eps(T)), i.e. ~3.4e-4 forFloat32and ~3.4e-2 forFloat16, versus ~1.5e-8 forFloat64. The starting point is much further from the axis.nsteps = ceil(2*(imstart - imz))is then 1 for all these cases, so the whole distance is covered in a single Taylor step ofEn_taylor.- the
while i == quick_niterloop that doublesimstartusesniter >> 4iterations irrespective of the precision.
So the number of steps and the truncation of the Taylor step are effectively tuned for Float64 while the starting distance grows as sqrt(eps(T)), and for Float32/Float16 the walk back to the axis is too coarse.
Not covered by the tests
test/expint.jl exercises the negative real axis only in Float64, so nothing catches this. Some Float32/Float16 points with real(z) < 0 compared against a BigFloat or Float64 reference would.
Note that the return type is currently widened to ComplexF64 even for ComplexF32 input, which hides the problem in a typeof check; that part is a separate (type stability) issue.
Filed by Claude Code on behalf of @andreasnoack; the investigation and the text above are Claude's.
- Dominant language
- Julia
- Stars
- 384
- Forks
- 115
- Avg merge
- 2d 9h
- Merged PRs (30d)
- 2
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from JuliaMath/SpecialFunctions.jl
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
JuliaMath/SpecialFunctions.jl#570 ·
-
Difficulty 5/5 Over a week Newbie friendliness 40/100
JuliaMath/SpecialFunctions.jl#569 · 2 comments ·
-
Difficulty 4/5 3-5 days Newbie friendliness 25/100
JuliaMath/SpecialFunctions.jl#567 ·
-
Difficulty 4/5 1-2 days Newbie friendliness 56/100
JuliaMath/SpecialFunctions.jl#563 ·
-
Difficulty 3/5 1-2 days Newbie friendliness 72/100
JuliaMath/SpecialFunctions.jl#560 ·
All issues in JuliaMath/SpecialFunctions.jl
Similar issues
-
backport 1.14 GC
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
Maintainers usually reply within 1 day
-
`binary_focal_loss` throws a `DomainError` when a negative sample has `ŷ = 1`Possibly taken @parth-patel010 claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
FluxML/Flux.jl#2818 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
JuliaDiff/TaylorSeries.jl#445 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
control-toolbox/CTActions#139 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
SciML/Optimization.jl#1448 ·
Maintainers usually reply within 1 day