JuliaMath / JuliaMath/SpecialFunctions.jl
besselj/bessely stack overflow for an integer order and a Float16 argument
- Dominant language
- Julia
- Stars
- 381
- Forks
- 113
- Avg merge
- 1d 23h
- Merged PRs (30d)
- 1
Description
`besselj` and `bessely` recurse infinitely and throw a `StackOverflowError` whenever the order is integer-valued and the argument is a `Float16`:
```julia
julia> using SpecialFunctions
julia> besselj(2, Float16(1))
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
ERROR: StackOverflowError:
julia> bessely(2, Float16(1))
ERROR: StackOverflowError:
```
Reproduced with SpecialFunctions v2.8.3 on Julia 1.10.12, 1.11.9 and 1.12.7.
What matters is that the order is integer-*valued*, not its type — all of these crash:
```julia
besselj(2, Float16(1)) # Int
besselj(Int32(2), Float16(1)) # Int32
besselj(true, Float16(1)) # Bool
besselj(2.0, Float16(1)) # Float64, integer-valued
besselj(Float16(2), Float16(1)) # Float16, integer-valued
```
while a non-integer order is fine (`besselj(Float16(3)/2, Float16(1)) == 0.2404`), and so is a `Complex{Float16}` argument (`besselj(2, Complex{Float16}(1, 1))`, although that one returns a `ComplexF64`).
## Cause
```julia
function besselj(nu::Real, x::AbstractFloat)
if isinteger(nu)
if typemin(Cint) <= nu <= typemax(Cint)
return besselj(Cint(nu), x)
end
elseif x < 0
throw(DomainError(x, "`x` must be nonnegative."))
end
real(besselj(float(nu), complex(x)))
end
```
The `isinteger` branch converts the order and calls `besselj` again, but leaves `x` untouched. The methods that are supposed to catch that call only exist for `Float32` and `Float64` arguments:
```julia
julia> filter(m -> occursin("Int32", string(m.sig)), methods(besselj))
Tuple{typeof(besselj), Int32, Float64}
Tuple{typeof(besselj), Int32, Float32}
```
so for a `Float16` argument the call lands back on `besselj(nu::Real, x::AbstractFloat)`, `isinteger(Cint(nu))` is still true, and it recurses forever. `bessely` is defined the same way.
This is not specific to `Float16`: any `AbstractFloat` without an `Int32`-order method takes the same path, so third-party types such as `Quadmath.Float128` or `DoubleFloats.Double64` would hit it too. `BigFloat` escapes only because it has its own `besselj(::Integer, ::BigFloat)` MPFR method.
The other members of the family (`besseli`, `besselk`, the scaled `*x` variants, the Hankel functions, `sphericalbessel*`) have no `isinteger` shortcut, so they do not crash — they go straight to the complex fall-through and instead silently widen the result to `Float64`:
```julia
julia> besseli(2, Float16(1))
0.13574766976703834 # Float64, not Float16
```
## Suggested fix
A `Float16` (or generally: convert-to-a-supported-precision-and-back) method for the integer-order case, mirroring what already exists for the complex methods
```julia
$bfn(nu::Float16, x::Complex{Float16}) = Complex{Float16}($bfn(Float32(nu), Complex{Float32}(x)))
```
would fix the crash. Note that the fall-through in the same function is also the source of the type instability of `besselj(2, 1.0f0)` and friends (it promotes the *order* with `float(nu)`, which is `Float64` for an `Integer` order, and drags the argument up with it), so it may be worth addressing both together. Ref https://github.com/JuliaMath/SpecialFunctions.jl/pull/234 and https://github.com/JuliaMath/SpecialFunctions.jl/pull/43.
---
*Filed by Claude Code on behalf of @andreasnoack; the investigation and the text above are Claude's.*
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by inspecting the besselj and bessely method definitions and reproduce the Float16 calls shown in the issue. Compare their integer-order dispatch with the existing Complex{Float16} conversion methods and verify that the fix removes the stack overflow without unintended widening; also check the related type-stability concern described in the issue.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- julia
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100