JuliaMath / JuliaMath/SpecialFunctions.jl

besselj/bessely stack overflow for an integer order and a Float16 argument

Open
#547 0 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.