450 lines
16 KiB
Go
450 lines
16 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
|
|
// SPDX-License-Identifier: MIT
|
|||
|
|
|
|||
|
|
package core
|
|||
|
|
|
|||
|
|
import "math"
|
|||
|
|
|
|||
|
|
// Ordinary Bessel functions of the first and second kind at one real
|
|||
|
|
// point, the scalar companions of the modified pair in besselmod.go.
|
|||
|
|
// J grows out of a convergent power series below the crossover and a
|
|||
|
|
// downward Miller recurrence above it, the direction that amplifies
|
|||
|
|
// no rounding once the order passes the argument. Y climbs the
|
|||
|
|
// upward recurrence from the seeds Y₀ and Y₁, the stable direction
|
|||
|
|
// for the second kind, with the seeds themselves from the Frobenius
|
|||
|
|
// series below the crossover and from the asymptotic expansion above
|
|||
|
|
// it, where the ascending series would start paying for cancellation.
|
|||
|
|
|
|||
|
|
// besselCrossover splits the power-series and recurrence regimes. At
|
|||
|
|
// the crossover both sides still carry twelve significant digits, so
|
|||
|
|
// the exact split point is a matter of taste rather than accuracy.
|
|||
|
|
const besselCrossover = 15.0
|
|||
|
|
|
|||
|
|
// BesselJ returns the Bessel function of the first kind of integer
|
|||
|
|
// order n at the real point x, Jₙ(x). Arguments with |x| below the
|
|||
|
|
// crossover are served by the convergent power series
|
|||
|
|
// Σ (−1)^k (x/2)^{2k+n}/(k!·Γ(k+n+1)); above it the recurrence runs in
|
|||
|
|
// its stable direction, orders at or below the argument climbing
|
|||
|
|
// upward from the large-argument asymptotic J₀ and J₁ at O(n) cost and
|
|||
|
|
// higher orders running the downward Miller walk anchored on the same
|
|||
|
|
// asymptotic J₀, whose start sits above the turning point at order
|
|||
|
|
// n + |x|. A negative argument follows the parity law
|
|||
|
|
// Jₙ(−x) = (−1)ⁿ Jₙ(x) and a negative order the law
|
|||
|
|
// J₋ₙ(x) = (−1)ⁿ Jₙ(x); Jₙ is finite for every real x, so nothing here
|
|||
|
|
// can fail.
|
|||
|
|
func BesselJ(n int, x float64) float64 {
|
|||
|
|
if x == 0 {
|
|||
|
|
if n == 0 {
|
|||
|
|
return 1
|
|||
|
|
}
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
// Fold both parity laws into one sign: (−1)ⁿ only cares about the
|
|||
|
|
// order's parity, which |n| preserves.
|
|||
|
|
order, ax, flip := n, math.Abs(x), 1.0
|
|||
|
|
if order < 0 {
|
|||
|
|
if order%2 != 0 {
|
|||
|
|
flip = -1
|
|||
|
|
}
|
|||
|
|
order = -order
|
|||
|
|
}
|
|||
|
|
if x < 0 && order%2 != 0 {
|
|||
|
|
flip = -flip
|
|||
|
|
}
|
|||
|
|
if ax < besselCrossover {
|
|||
|
|
return flip * besselJSeries(order, ax)
|
|||
|
|
}
|
|||
|
|
if float64(order) <= ax {
|
|||
|
|
return flip * besselJUpward(order, ax)
|
|||
|
|
}
|
|||
|
|
return flip * besselJMiller(order, ax)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// BesselY returns the Bessel function of the second kind of integer
|
|||
|
|
// order n at the real point x, Yₙ(x), defined for x > 0; Y diverges
|
|||
|
|
// at the origin and a non-positive argument is an error, not a NaN.
|
|||
|
|
// Y₀ and Y₁ are seeded below the crossover by the Frobenius series
|
|||
|
|
// (Abramowitz & Stegun 9.1.11 with ψ(k+1) = H_k − γ written out
|
|||
|
|
// through the harmonic numbers H_k) and above it by the
|
|||
|
|
// large-argument asymptotic expansion; higher orders climb the upward
|
|||
|
|
// recurrence Yₙ₊₁ = 2n/x·Yₙ − Yₙ₋₁, stable for the second kind
|
|||
|
|
// because the parasitic J component the seeds carry decays relative
|
|||
|
|
// to Y at every step. A negative order follows the parity law
|
|||
|
|
// Y₋ₙ(x) = (−1)ⁿ Yₙ(x).
|
|||
|
|
//
|
|||
|
|
// Errors: x ≤ 0 or NaN.
|
|||
|
|
func BesselY(n int, x float64) (float64, error) {
|
|||
|
|
if math.IsNaN(x) || x <= 0 {
|
|||
|
|
return 0, errf("BesselY: the argument must be positive, got %g", x)
|
|||
|
|
}
|
|||
|
|
flip, order := 1.0, n
|
|||
|
|
if order < 0 {
|
|||
|
|
if order%2 != 0 {
|
|||
|
|
flip = -1
|
|||
|
|
}
|
|||
|
|
order = -order
|
|||
|
|
}
|
|||
|
|
var v float64
|
|||
|
|
switch {
|
|||
|
|
case order == 0:
|
|||
|
|
v = besselY0(x)
|
|||
|
|
case order == 1:
|
|||
|
|
v = besselY1(x)
|
|||
|
|
default:
|
|||
|
|
ym, y := besselY0(x), besselY1(x)
|
|||
|
|
for k := 1; k < order; k++ {
|
|||
|
|
ym, y = y, 2*float64(k)/x*y-ym
|
|||
|
|
}
|
|||
|
|
v = y
|
|||
|
|
}
|
|||
|
|
return flip * v, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// besselJSeries evaluates Jₙ(x) by the convergent power series,
|
|||
|
|
// stepping the summand along tₖ = tₖ₋₁·(−(x/2)²)/(k(k+n)). The first
|
|||
|
|
// term (x/2)ⁿ/n! goes through logarithms so a large order never
|
|||
|
|
// overflows the factorial on the way to a small answer.
|
|||
|
|
func besselJSeries(n int, x float64) float64 {
|
|||
|
|
half := 0.5 * x
|
|||
|
|
// The k = 0 term (x/2)ⁿ/n! is positive; the (−1)^k alternation
|
|||
|
|
// enters through the recursion step below.
|
|||
|
|
t := math.Exp(float64(n)*math.Log(half) - LnFactorial(n))
|
|||
|
|
sum := t
|
|||
|
|
q := half * half
|
|||
|
|
for k := 1; k <= 400; k++ {
|
|||
|
|
t *= -q / (float64(k) * float64(k+n))
|
|||
|
|
sum += t
|
|||
|
|
if math.Abs(t) <= 1e-17*math.Abs(sum) {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return sum
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// besselJUpward climbs J₀ and J₁ from the large-argument asymptotic to
|
|||
|
|
// order n by the upward recurrence Jₖ₊₁ = (2k/x)·Jₖ − Jₖ₋₁. The
|
|||
|
|
// asymptotic seeds carry the absolute scale, so nothing needs
|
|||
|
|
// renormalising, and the climb is the stable direction while the order
|
|||
|
|
// stays at or below the argument: there the parasitic Y component the
|
|||
|
|
// seeds carry stays bounded relative to J, while the downward walk
|
|||
|
|
// would start above the turning point at order x and pay O(x) steps
|
|||
|
|
// for an answer of order n. The caller guarantees x ≥ besselCrossover
|
|||
|
|
// and 0 ≤ n ≤ x.
|
|||
|
|
func besselJUpward(n int, x float64) float64 {
|
|||
|
|
j0, _ := besselAsymptotic(0, x)
|
|||
|
|
if n == 0 {
|
|||
|
|
return j0
|
|||
|
|
}
|
|||
|
|
j1, _ := besselAsymptotic(1, x)
|
|||
|
|
for k := 1; k < n; k++ {
|
|||
|
|
j0, j1 = j1, 2*float64(k)/x*j1-j0
|
|||
|
|
}
|
|||
|
|
return j1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// besselJMiller evaluates Jₙ(x) for |x| above the crossover by the
|
|||
|
|
// downward Miller recurrence, the same stable scheme
|
|||
|
|
// besselInMiller uses for the modified kind: start well above n with
|
|||
|
|
// an arbitrary scale, recurse down through J_{k−1} = (2k/x)·J_k −
|
|||
|
|
// J_{k+1}, then renormalise the arbitrary seed scale against an
|
|||
|
|
// independent J₀, here the asymptotic value the way besselInMiller
|
|||
|
|
// leans on its quadrature I₀. The caller guarantees x ≠ 0.
|
|||
|
|
func besselJMiller(n int, x float64) float64 {
|
|||
|
|
start := n + int(x) + 40
|
|||
|
|
jp, j := 0.0, 1.0 // J_{k+1}, J_k, seeded at k = start
|
|||
|
|
jn := 0.0
|
|||
|
|
for k := start; k >= 1; k-- {
|
|||
|
|
if k == n {
|
|||
|
|
jn = j
|
|||
|
|
}
|
|||
|
|
jp, j = j, 2*float64(k)/x*j-jp
|
|||
|
|
if aj := math.Abs(j); aj > 1e200 {
|
|||
|
|
// The unscaled seed grows like k!/x^k on the way down and
|
|||
|
|
// would overflow for high orders whose true value is
|
|||
|
|
// representable; the renormalisation cancels any common
|
|||
|
|
// factor, so rescaling the running pair (and the captured
|
|||
|
|
// order-n value) is exact up to rounding.
|
|||
|
|
jp /= aj
|
|||
|
|
j /= aj
|
|||
|
|
jn /= aj
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if n == 0 {
|
|||
|
|
jn = j
|
|||
|
|
}
|
|||
|
|
anchor, _ := besselAsymptotic(0, x)
|
|||
|
|
return anchor * jn / j
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// besselY0 evaluates Y₀(x) for x > 0. Below the crossover the
|
|||
|
|
// Frobenius series in the form the digamma reduction gives,
|
|||
|
|
//
|
|||
|
|
// Y₀ = (2/π)·[(ln(x/2) + γ)·J₀(x) + Σ (−1)^{k+1} H_k (x/2)^{2k}/(k!)²],
|
|||
|
|
//
|
|||
|
|
// above it the asymptotic expansion, whose terms still fall fast
|
|||
|
|
// enough at the crossover to keep twelve significant digits.
|
|||
|
|
func besselY0(x float64) float64 {
|
|||
|
|
if x >= besselCrossover {
|
|||
|
|
_, y := besselAsymptotic(0, x)
|
|||
|
|
return y
|
|||
|
|
}
|
|||
|
|
half := 0.5 * x
|
|||
|
|
q := half * half
|
|||
|
|
u := q // u_k = (x/2)^{2k}/(k!)², starting at k = 1
|
|||
|
|
h := 1.0 // H_k
|
|||
|
|
sum := u // the k = 1 term carries the + sign
|
|||
|
|
for k := 2; k <= 200; k++ {
|
|||
|
|
u *= q / float64(k*k)
|
|||
|
|
h += 1 / float64(k)
|
|||
|
|
term := h * u
|
|||
|
|
if k%2 == 1 {
|
|||
|
|
sum += term
|
|||
|
|
} else {
|
|||
|
|
sum -= term
|
|||
|
|
}
|
|||
|
|
if math.Abs(term) <= 1e-17*math.Abs(sum) {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return (2 / math.Pi) * ((math.Log(half)+eulerGamma)*besselJSeries(0, x) + sum)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// besselY1 evaluates Y₁(x) for x > 0, the order-one twin of
|
|||
|
|
// besselY0's series branch:
|
|||
|
|
//
|
|||
|
|
// Y₁ = (2/π)(ln(x/2) + γ)·J₁(x) − 2/(πx)
|
|||
|
|
// − (1/π)·Σ (−1)^k (H_k + H_{k+1})·(x/2)^{2k+1}/(k!(k+1)!),
|
|||
|
|
//
|
|||
|
|
// where the 2/(πx) term is the one-entry finite sum of A&S 9.1.11
|
|||
|
|
// and the ascending series converges for every x, paying only the
|
|||
|
|
// cancellation that caps its usable range at the crossover.
|
|||
|
|
func besselY1(x float64) float64 {
|
|||
|
|
if x >= besselCrossover {
|
|||
|
|
_, y := besselAsymptotic(1, x)
|
|||
|
|
return y
|
|||
|
|
}
|
|||
|
|
half := 0.5 * x
|
|||
|
|
q := half * half
|
|||
|
|
u := half // u_k = (x/2)^{2k+1}/(k!(k+1)!), starting at k = 0
|
|||
|
|
h := 1.0 // H_{k+1}, starting at H_1 = 1 (H_0 = 0)
|
|||
|
|
sum := u // the k = 0 term: (H_0 + H_1)·u₀ with the + sign
|
|||
|
|
for k := 1; k <= 200; k++ {
|
|||
|
|
hk := h // H_k, before the update below
|
|||
|
|
u *= q / float64(k*(k+1))
|
|||
|
|
h += 1 / float64(k+1)
|
|||
|
|
term := (hk + h) * u
|
|||
|
|
if k%2 == 1 {
|
|||
|
|
sum -= term
|
|||
|
|
} else {
|
|||
|
|
sum += term
|
|||
|
|
}
|
|||
|
|
if math.Abs(term) <= 1e-17*math.Abs(sum) {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return (2/math.Pi)*((math.Log(half)+eulerGamma)*besselJSeries(1, x)) -
|
|||
|
|
2/(math.Pi*x) - sum/math.Pi
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// besselAsymptotic evaluates the pair J_ν, Y_ν above the crossover
|
|||
|
|
// from the large-argument expansion (Abramowitz & Stegun 9.2.5 through
|
|||
|
|
// 9.2.6):
|
|||
|
|
//
|
|||
|
|
// J_ν ~ sqrt(2/πx)·[cos ω·Σ(−1)^k a_{2k} − sin ω·Σ(−1)^k a_{2k+1}]
|
|||
|
|
// Y_ν ~ sqrt(2/πx)·[sin ω·Σ(−1)^k a_{2k} + cos ω·Σ(−1)^k a_{2k+1}],
|
|||
|
|
//
|
|||
|
|
// with ω = x − νπ/2 − π/4 and aₘ = ∏(μ − (2s−1)²)/(m!·(8x)^m),
|
|||
|
|
// μ = 4ν². The order is a float64: the integer callers pass exact
|
|||
|
|
// integers, whose float64 arithmetic reproduces the int path bit for
|
|||
|
|
// bit, and the real-order BesselJRealOrder passes the fractional seed
|
|||
|
|
// orders. Both kinds share the one coefficient sweep, and the sum
|
|||
|
|
// stops at the optimal truncation where the terms turn around and
|
|||
|
|
// start growing again, which at the crossover still leaves about
|
|||
|
|
// twelve significant digits.
|
|||
|
|
func besselAsymptotic(nu float64, x float64) (j, y float64) {
|
|||
|
|
mu := 4 * nu * nu
|
|||
|
|
omega := besselPhase(nu, x)
|
|||
|
|
eighth := 8 * x
|
|||
|
|
a, prev := 1.0, math.Inf(1)
|
|||
|
|
even, odd := 1.0, 0.0 // Σ(−1)^k a_{2k}, Σ(−1)^k a_{2k+1}
|
|||
|
|
for m := 1; m <= 200; m++ {
|
|||
|
|
a *= (mu - float64((2*m-1)*(2*m-1))) / (float64(m) * eighth)
|
|||
|
|
// m = 2k and m = 2k+1 share the integer k = m/2 and the sign
|
|||
|
|
// (−1)^k of their sum's k-th term.
|
|||
|
|
if (m/2)%2 == 0 {
|
|||
|
|
if m%2 == 0 {
|
|||
|
|
even += a
|
|||
|
|
} else {
|
|||
|
|
odd += a
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if m%2 == 0 {
|
|||
|
|
even -= a
|
|||
|
|
} else {
|
|||
|
|
odd -= a
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if math.Abs(a) > prev {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
prev = math.Abs(a)
|
|||
|
|
}
|
|||
|
|
factor := math.Sqrt(2 / (math.Pi * x))
|
|||
|
|
sin, cos := math.Sin(omega), math.Cos(omega)
|
|||
|
|
j = factor * (cos*even - sin*odd)
|
|||
|
|
y = factor * (sin*even + cos*odd)
|
|||
|
|
return j, y
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2π split into two float64 parts, the sum of which is 2π to about
|
|||
|
|
// 1e-32: the phase reduction below needs the low part to hold the
|
|||
|
|
// fraction of a large argument.
|
|||
|
|
const (
|
|||
|
|
twoPiHi = 6.283185307179586 // fl(2π)
|
|||
|
|
twoPiLo = 2.4492935982947064e-16 // 2π − fl(2π)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// besselPhase returns ω = x − νπ/2 − π/4 reduced modulo 2π for the
|
|||
|
|
// large-argument expansion. The reduction is what keeps the phase of a
|
|||
|
|
// large argument meaningful: the plain float64 difference rounds the
|
|||
|
|
// fraction of x away, at x = 1e9 to about 1e-7 absolute, which the
|
|||
|
|
// amplitude √(2/πx) turns into a relative error of 1e-8, and at
|
|||
|
|
// x = 1e12 into 1e-4. The remainder is taken with the two-part 2π:
|
|||
|
|
// x − hi is exact by Sterbenz's lemma, math.FMA gives the exact
|
|||
|
|
// residual of n·2π, and the rest is a handful of flops on quantities
|
|||
|
|
// below π, so the phase keeps its own last ulp while |ω| < 2^53·2π
|
|||
|
|
// (beyond that the float64 grid of x is coarser than a radian and the
|
|||
|
|
// plain difference is as good as anything). The caller passes |x|,
|
|||
|
|
// which is at least the crossover.
|
|||
|
|
func besselPhase(nu float64, x float64) float64 {
|
|||
|
|
nuPi2 := nu * math.Pi / 2
|
|||
|
|
quarterPi := math.Pi / 4
|
|||
|
|
omega := x - nuPi2 - quarterPi
|
|||
|
|
n := math.Round(omega / twoPiHi)
|
|||
|
|
if math.Abs(n) >= 1<<53 {
|
|||
|
|
return omega
|
|||
|
|
}
|
|||
|
|
hi := n * twoPiHi
|
|||
|
|
lo := math.FMA(n, twoPiHi, -hi)
|
|||
|
|
return x - hi - lo - n*twoPiLo - nuPi2 - quarterPi
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// BesselJRealOrder returns the Bessel function of the first kind of
|
|||
|
|
// real order ν at the real point x, J_ν(x). The order must be ≥ 0 and
|
|||
|
|
// the argument positive; J diverges at the origin for ν > 0 and a
|
|||
|
|
// non-positive argument or a NaN order is an error, not a NaN.
|
|||
|
|
//
|
|||
|
|
// The regimes follow the integer BesselJ's, with the fractional part
|
|||
|
|
// of the order taking the role of the seeds: below the crossover the
|
|||
|
|
// ascending Frobenius series carries the answer, above it the order
|
|||
|
|
// resolves into its integer and fractional parts, the fractional pair
|
|||
|
|
// (ν₀, ν₀+1) is seeded from the large-argument expansion and the
|
|||
|
|
// three-term recurrence runs in its stable direction, upward while the
|
|||
|
|
// order stays at or below the argument and by the downward Miller walk
|
|||
|
|
// anchored on the ν₀ seed when the order passes it. Orders within 1e-8
|
|||
|
|
// of a non-negative integer are served by the integer algorithm
|
|||
|
|
// itself, which is the continuous limit there: the general route's
|
|||
|
|
// normalisation would cancel against sin(πν) and lose every digit.
|
|||
|
|
func BesselJRealOrder(nu, x float64) (float64, error) {
|
|||
|
|
if math.IsNaN(nu) || math.IsNaN(x) {
|
|||
|
|
return 0, errf("BesselJRealOrder: the order and the argument must be finite, got %g and %g", nu, x)
|
|||
|
|
}
|
|||
|
|
if nu < 0 {
|
|||
|
|
return 0, errf("BesselJRealOrder: the order must be zero or greater, got %g", nu)
|
|||
|
|
}
|
|||
|
|
if x <= 0 {
|
|||
|
|
return 0, errf("BesselJRealOrder: the argument must be positive, got %g", x)
|
|||
|
|
}
|
|||
|
|
if n := math.Round(nu); math.Abs(nu-n) <= 1e-8 {
|
|||
|
|
return BesselJ(int(n), x), nil
|
|||
|
|
}
|
|||
|
|
if x < besselRealCrossover {
|
|||
|
|
return besselJSeriesReal(nu, x), nil
|
|||
|
|
}
|
|||
|
|
whole := math.Floor(nu)
|
|||
|
|
frac := nu - whole
|
|||
|
|
if nu <= x {
|
|||
|
|
// The climb: seed the fractional pair from the expansion and
|
|||
|
|
// run the upward recurrence, the stable direction while the
|
|||
|
|
// order stays at or below the argument. whole = 0 means the
|
|||
|
|
// answer is the first seed itself.
|
|||
|
|
jm, _ := besselAsymptotic(frac, x)
|
|||
|
|
if whole == 0 {
|
|||
|
|
return jm, nil
|
|||
|
|
}
|
|||
|
|
j, _ := besselAsymptotic(frac+1, x)
|
|||
|
|
for k := 1; k < int(whole); k++ {
|
|||
|
|
jm, j = j, 2*(frac+float64(k))/x*j-jm
|
|||
|
|
}
|
|||
|
|
return j, nil
|
|||
|
|
}
|
|||
|
|
// The downward Miller walk through one fractional residue class,
|
|||
|
|
// anchored on the fractional seed the same way besselJMiller
|
|||
|
|
// anchors its integer walk on J₀. The walk counts integer steps
|
|||
|
|
// from the fractional floor instead of testing k against nu and
|
|||
|
|
// frac: start descends in floats, and a capture missed by one ulp
|
|||
|
|
// of the running k would answer from an unseeded slot.
|
|||
|
|
steps := int(math.Floor(x)) + int(whole) + 40
|
|||
|
|
jp, j := 0.0, 1.0 // J_{k+1}, J_k, seeded at k = frac + steps
|
|||
|
|
uNu := 0.0
|
|||
|
|
uFrac := 0.0
|
|||
|
|
for i := steps; i >= 0; i-- {
|
|||
|
|
k := frac + float64(i)
|
|||
|
|
if i == int(whole) {
|
|||
|
|
uNu = j
|
|||
|
|
}
|
|||
|
|
if i == 0 {
|
|||
|
|
uFrac = j
|
|||
|
|
}
|
|||
|
|
jp, j = j, 2*k/x*j-jp
|
|||
|
|
if aj := math.Abs(j); aj > 1e200 {
|
|||
|
|
jp /= aj
|
|||
|
|
j /= aj
|
|||
|
|
uNu /= aj
|
|||
|
|
uFrac /= aj
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
anchor, _ := besselAsymptotic(frac, x)
|
|||
|
|
return anchor * uNu / uFrac, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// lnGammaReal returns lnΓ(z) for z > 0 at one point, the scalar
|
|||
|
|
// companion the real-order series needs; the sign of Γ is positive on
|
|||
|
|
// that domain, so only the logarithm comes back.
|
|||
|
|
func lnGammaReal(z float64) float64 {
|
|||
|
|
lg, _ := math.Lgamma(z)
|
|||
|
|
return lg
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// besselRealCrossover splits the real-order series and recurrence
|
|||
|
|
// regimes, lower than the integer one because the two sides' quality
|
|||
|
|
// decides differently here: the series pays the same cancellation
|
|||
|
|
// (about x·ln10/2 digits at x = 12, still leaving better than nine),
|
|||
|
|
// while the expansion the seeds come from truncates to full precision
|
|||
|
|
// for the half-integer orders and to ten-plus digits for the small
|
|||
|
|
// fractional seeds the climb and the Miller walk lean on.
|
|||
|
|
const besselRealCrossover = 12.0
|
|||
|
|
|
|||
|
|
// besselJSeriesReal evaluates J_ν(x) by the convergent Frobenius
|
|||
|
|
// series for real ν, stepping the summand along
|
|||
|
|
// tₖ = tₖ₋₁·(−(x/2)²)/(k(k+ν)). It is the real-order companion of
|
|||
|
|
// besselJSeries, kept separate so the integer series keeps its
|
|||
|
|
// exact LnFactorial opening and its recorded bits.
|
|||
|
|
func besselJSeriesReal(nu, x float64) float64 {
|
|||
|
|
half := 0.5 * x
|
|||
|
|
// The opening term never overflows below the crossover: with
|
|||
|
|
// half < 7.5 the exponent ν·log(half) − lnΓ(ν+1) turns downward
|
|||
|
|
// past ν ≈ 20 and stays negative.
|
|||
|
|
t := math.Exp(nu*math.Log(half) - lnGammaReal(nu+1))
|
|||
|
|
sum := t
|
|||
|
|
q := half * half
|
|||
|
|
for k := 1; k <= 400; k++ {
|
|||
|
|
t *= -q / (float64(k) * (float64(k) + nu))
|
|||
|
|
sum += t
|
|||
|
|
if math.Abs(t) <= 1e-17*math.Abs(sum) {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return sum
|
|||
|
|
}
|