Skip to content

Commit df609d1

Browse files
authored
Merge pull request #40 from JuliaMath/bessely_nan_error
Fix #35 and correct for NaN return for small arguments in bessely
2 parents ea701f7 + df2db0c commit df609d1

File tree

11 files changed

+80
-18
lines changed

11 files changed

+80
-18
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ For bug fixes, performance enhancements, or fixes to unexported functions we wil
1818

1919
### Fixed
2020
- fix cutoff in `bessely` to not return error for integer orders and small arguments (#33)
21+
- fix NaN return for small arguments (issue [#35]) in bessely (#40)
22+
- allow calling with integer argument and add float16 support (#40)
2123

2224
# Version 0.1.0
2325

src/besseli.jl

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,12 @@ end
164164
165165
Modified Bessel function of the second kind of order nu, ``I_{nu}(x)``.
166166
"""
167-
function besseli(nu::Real, x::T) where T
168-
isinteger(nu) && return besseli(Int(nu), x)
167+
besseli(nu::Real, x::Real) = _besseli(nu, float(x))
168+
169+
_besseli(nu, x::Float16) = Float16(_besseli(nu, Float32(x)))
170+
171+
function _besseli(nu, x::T) where T <: Union{Float32, Float64}
172+
isinteger(nu) && return _besseli(Int(nu), x)
169173
abs_nu = abs(nu)
170174
abs_x = abs(x)
171175

@@ -187,7 +191,7 @@ function besseli(nu::Real, x::T) where T
187191
end
188192
end
189193
end
190-
function besseli(nu::Integer, x::T) where T
194+
function _besseli(nu::Integer, x::T) where T <: Union{Float32, Float64}
191195
abs_nu = abs(nu)
192196
abs_x = abs(x)
193197
sg = iseven(abs_nu) ? 1 : -1
@@ -225,7 +229,11 @@ end
225229
Scaled modified Bessel function of the first kind of order nu, ``I_{nu}(x)*e^{-x}``.
226230
Nu must be real.
227231
"""
228-
function besselix(nu, x::T) where T <: Union{Float32, Float64}
232+
besselix(nu::Real, x::Real) = _besselix(nu, float(x))
233+
234+
_besselix(nu, x::Float16) = Float16(_besselix(nu, Float32(x)))
235+
236+
function _besselix(nu, x::T) where T <: Union{Float32, Float64}
229237
iszero(nu) && return besseli0x(x)
230238
isone(nu) && return besseli1x(x)
231239

src/besselj.jl

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,18 @@ end
192192
##### Generic routine for `besselj`
193193
#####
194194

195-
function besselj(nu::Real, x::T) where T
196-
isinteger(nu) && return besselj(Int(nu), x)
195+
"""
196+
besselj(nu, x::T) where T <: Union{Float32, Float64}
197+
198+
Bessel function of the first kind of order nu, ``J_{nu}(x)``.
199+
nu and x must be real where nu and x can be positive or negative.
200+
"""
201+
besselj(nu::Real, x::Real) = _besselj(nu, float(x))
202+
203+
_besselj(nu, x::Float16) = Float16(_besselj(nu, Float32(x)))
204+
205+
function _besselj(nu, x::T) where T <: Union{Float32, Float64}
206+
isinteger(nu) && return _besselj(Int(nu), x)
197207
abs_nu = abs(nu)
198208
abs_x = abs(x)
199209

@@ -218,7 +228,7 @@ function besselj(nu::Real, x::T) where T
218228
end
219229
end
220230

221-
function besselj(nu::Integer, x::T) where T
231+
function _besselj(nu::Integer, x::T) where T <: Union{Float32, Float64}
222232
abs_nu = abs(nu)
223233
abs_x = abs(x)
224234
sg = iseven(abs_nu) ? 1 : -1

src/besselk.jl

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,12 @@ end
185185
186186
Modified Bessel function of the second kind of order nu, ``K_{nu}(x)``.
187187
"""
188-
function besselk(nu::Real, x::T) where T
189-
isinteger(nu) && return besselk(Int(nu), x)
188+
besselk(nu::Real, x::Real) = _besselk(nu, float(x))
189+
190+
_besselk(nu, x::Float16) = Float16(_besselk(nu, Float32(x)))
191+
192+
function _besselk(nu, x::T) where T <: Union{Float32, Float64}
193+
isinteger(nu) && return _besselk(Int(nu), x)
190194
abs_nu = abs(nu)
191195
abs_x = abs(x)
192196

@@ -197,7 +201,7 @@ function besselk(nu::Real, x::T) where T
197201
#return cispi(-abs_nu)*besselk_positive_args(abs_nu, abs_x) - besseli_positive_args(abs_nu, abs_x) * im * π
198202
end
199203
end
200-
function besselk(nu::Integer, x::T) where T
204+
function _besselk(nu::Integer, x::T) where T <: Union{Float32, Float64}
201205
abs_nu = abs(nu)
202206
abs_x = abs(x)
203207
sg = iseven(abs_nu) ? 1 : -1
@@ -239,7 +243,11 @@ end
239243
240244
Scaled modified Bessel function of the second kind of order nu, ``K_{nu}(x)*e^{x}``.
241245
"""
242-
function besselkx(nu, x::T) where T <: Union{Float32, Float64}
246+
besselkx(nu::Real, x::Real) = _besselkx(nu, float(x))
247+
248+
_besselkx(nu, x::Float16) = Float16(_besselkx(nu, Float32(x)))
249+
250+
function _besselkx(nu, x::T) where T <: Union{Float32, Float64}
243251
# dispatch to avoid uniform expansion when nu = 0
244252
iszero(nu) && return besselk0x(x)
245253

src/bessely.jl

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,13 @@ end
235235
Bessel function of the second kind of order nu, ``Y_{nu}(x)``.
236236
nu and x must be real where nu and x can be positive or negative.
237237
"""
238-
function bessely(nu::Real, x::T) where T
238+
bessely(nu::Real, x::Real) = _bessely(nu, float(x))
239+
240+
_bessely(nu, x::Float16) = Float16(_bessely(nu, Float32(x)))
241+
242+
function _bessely(nu, x::T) where T <: Union{Float32, Float64}
239243
isnan(nu) || isnan(x) && return NaN
240-
isinteger(nu) && return bessely(Int(nu), x)
244+
isinteger(nu) && return _bessely(Int(nu), x)
241245
abs_nu = abs(nu)
242246
abs_x = abs(x)
243247

@@ -260,7 +264,7 @@ function bessely(nu::Real, x::T) where T
260264
end
261265
end
262266
end
263-
function bessely(nu::Integer, x::T) where T
267+
function _bessely(nu::Integer, x::T) where T <: Union{Float32, Float64}
264268
abs_nu = abs(nu)
265269
abs_x = abs(x)
266270
sg = iseven(abs_nu) ? 1 : -1
@@ -336,6 +340,8 @@ end
336340
# this works well for small arguments x < 7.0 for rel. error ~1e-14
337341
# this also works well for nu > 1.35x - 4.5
338342
# for nu > 25 more cancellation occurs near integer values
343+
# There could be premature underflow when (x/2)^v == 0.
344+
# It might be better to use logarithms (when we get loggamma julia implementation)
339345
"""
340346
bessely_power_series(nu, x::T) where T <: Float64
341347
@@ -348,6 +354,9 @@ function bessely_power_series(v, x::T) where T
348354
out = zero(T)
349355
out2 = zero(T)
350356
a = (x/2)^v
357+
# check for underflow and return limit for small arguments
358+
iszero(a) && return (-T(Inf), a)
359+
351360
b = inv(a)
352361
a /= gamma(v + one(T))
353362
b /= gamma(-v + one(T))

src/recurrence.jl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,19 @@ end
1515
# outputs both (bessel(x, nu_end), bessel(x, nu_end+1)
1616
# x = 0.1; y0 = bessely0(x); y1 = bessely1(x);
1717
# besselj_up_recurrence(x, y1, y0, 1, 5) will give bessely(5, x)
18-
@inline function besselj_up_recurrence(x, jnu, jnum1, nu_start, nu_end)
18+
@inline function besselj_up_recurrence(x::T, jnu, jnum1, nu_start, nu_end) where T
1919
x2 = 2 / x
2020
while nu_start < nu_end + 0.5 # avoid inexact floating points when nu isa float
2121
jnum1, jnu = jnu, muladd(nu_start*x2, jnu, -jnum1)
2222
nu_start += 1
2323
end
24-
return jnum1, jnu
24+
# need to check if NaN resulted during loop from subtraction of infinities
25+
# this could happen if x is very small and nu is large which eventually results in overflow (-> -Inf)
26+
# we use this routine to generate bessely(nu::Int, x) in the forward direction starting from bessely0, bessely1
27+
# NaN inputs need to be checked before getting to this routine
28+
return isnan(jnum1) ? (-T(Inf), -T(Inf)) : (jnum1, jnu)
2529
end
30+
2631
# backward recurrence relation for besselj and bessely
2732
# outputs both (bessel(x, nu_end), bessel(x, nu_end-1)
2833
# x = 0.1; j10 = besselj(10, x); j11 = besselj(11, x);

test/besseli_test.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ end
8080
#Float 64
8181
m = 0:1:200; x = 0.1:0.5:150.0
8282
@test besseli(10, 1.0) isa Float64
83+
@test besseli(10, Float16(1.0)) isa Float16
84+
8385
@test besseli(2, 80.0) isa Float64
8486
@test besseli(112, 80.0) isa Float64
8587
t = [besseli(m, x) for m in m, x in x]
@@ -90,6 +92,7 @@ t = [besseli(m, x) for m in m, x in x]
9092
t = [besselix(m, x) for m in m, x in x]
9193
@test t[10] isa Float64
9294
@test t [SpecialFunctions.besselix(m, x) for m in m, x in x]
95+
@test besselix(10, Float16(1.0)) isa Float16
9396

9497
## Tests for besselk
9598

test/besselj_test.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ for v in nu, xx in x
110110
@test isapprox(Bessels.besseljy_positive_args(v, xx)[1], Float64(sf), rtol=5e-11)
111111
end
112112

113+
# test Float16
114+
@test besselj(10, Float16(1.0)) isa Float16
115+
113116
## test large arguments
114117
@test isapprox(besselj(10.0, 150.0), SpecialFunctions.besselj(10.0, 150.0), rtol=1e-12)
115118

test/besselk_test.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ for v in nu, xx in x
120120
@test isapprox(besselk(v, xx), Float64(sf), rtol=2e-13)
121121
end
122122

123+
# test Float16
124+
@test besselk(10, Float16(1.0)) isa Float16
125+
@test besselkx(10, Float16(1.0)) isa Float16
126+
123127
# test Inf
124128
@test iszero(besselk(2, Inf))
125129

test/bessely_test.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,16 @@ for v in nu, xx in x
8989
@test isapprox(Bessels.besseljy_positive_args(v, xx)[2], SpecialFunctions.bessely(v, xx), rtol=5e-12)
9090
end
9191

92+
# test Float16
93+
@test bessely(10, Float16(1.0)) isa Float16
94+
95+
# test limits for small arguments see https://github.com/JuliaMath/Bessels.jl/issues/35
96+
@test bessely(185.0, 1.01) == -Inf
97+
@test bessely(185.5, 1.01) == -Inf
98+
@test bessely(2.0, 1e-300) == -Inf
99+
@test bessely(4.0, 1e-80) == -Inf
100+
@test bessely(4.5, 1e-80) == -Inf
101+
92102
# need to test accuracy of negative orders and negative arguments and all combinations within
93103
# SpecialFunctions.jl doesn't provide these so will hand check against hard values
94104
# values taken from https://keisan.casio.com/exec/system/1180573474 which match mathematica

0 commit comments

Comments
 (0)