pxfm/bessel/j1f.rs
1/*
2 * // Copyright (c) Radzivon Bartoshyk 7/2025. All rights reserved.
3 * //
4 * // Redistribution and use in source and binary forms, with or without modification,
5 * // are permitted provided that the following conditions are met:
6 * //
7 * // 1. Redistributions of source code must retain the above copyright notice, this
8 * // list of conditions and the following disclaimer.
9 * //
10 * // 2. Redistributions in binary form must reproduce the above copyright notice,
11 * // this list of conditions and the following disclaimer in the documentation
12 * // and/or other materials provided with the distribution.
13 * //
14 * // 3. Neither the name of the copyright holder nor the names of its
15 * // contributors may be used to endorse or promote products derived from
16 * // this software without specific prior written permission.
17 * //
18 * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 * // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 * // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29use crate::bessel::j1_coeffs::{J1_ZEROS, J1_ZEROS_VALUE};
30use crate::bessel::j1f_coeffs::J1F_COEFFS;
31use crate::bessel::trigo_bessel::sin_small;
32use crate::double_double::DoubleDouble;
33use crate::polyeval::{f_polyeval7, f_polyeval10, f_polyeval12, f_polyeval14};
34use crate::sincos_reduce::rem2pif_any;
35
36/// Bessel of the first kind of order 1
37///
38/// Max ULP 0.5
39///
40/// Note about accuracy:
41/// - Close to zero Bessel have tiny values such that testing against MPFR must be done exactly
42/// in the same precision, since any nearest representable number have ULP > 0.5.
43/// For example `J1(0.000000000000000000000000000000000000023509886)` in single precision
44/// have an error at least 0.72 ULP for any number with extended precision,
45/// that would be represented in f32.
46pub fn f_j1f(x: f32) -> f32 {
47 let ux = x.to_bits().wrapping_shl(1);
48 if ux >= 0xffu32 << 24 || ux == 0 {
49 // |x| == 0, |x| == inf, |x| == NaN
50 if ux == 0 {
51 // |x| == 0
52 return x;
53 }
54 if x.is_infinite() {
55 return 0.;
56 }
57 return x + f32::NAN; // x == NaN
58 }
59
60 let ax = x.to_bits() & 0x7fff_ffff;
61
62 if ax < 0x429533c2u32 {
63 // |x| <= 74.60109
64 if ax < 0x3e800000u32 {
65 // |x| <= 0.25
66 if ax <= 0x34000000u32 {
67 // |x| <= f32::EPSILON
68 // taylor series for J1(x) ~ x/2 + O(x^3)
69 return x * 0.5;
70 }
71 return poly_near_zero(x);
72 }
73 return small_argument_path(x);
74 }
75
76 // Exceptional cases:
77 if ax == 0x6ef9be45 {
78 return if x.is_sign_negative() {
79 f32::from_bits(0x187d8a8f)
80 } else {
81 -f32::from_bits(0x187d8a8f)
82 };
83 } else if ax == 0x7f0e5a38 {
84 return if x.is_sign_negative() {
85 -f32::from_bits(0x131f680b)
86 } else {
87 f32::from_bits(0x131f680b)
88 };
89 }
90
91 j1f_asympt(x) as f32
92}
93
94#[inline]
95fn j1f_rsqrt(x: f64) -> f64 {
96 (1. / x) * x.sqrt()
97}
98
99/*
100 Evaluates:
101 J1 = sqrt(2/(PI*x)) * beta(x) * cos(x - 3*PI/4 - alpha(x))
102 discarding 1*PI/2 using identities gives:
103 J1 = sqrt(2/(PI*x)) * beta(x) * sin(x - PI/4 - alpha(x))
104
105 to avoid squashing small (-PI/4 - alpha(x)) into a large x actual expansion is:
106
107 J1 = sqrt(2/(PI*x)) * beta(x) * sin((x mod 2*PI) - PI/4 - alpha(x))
108*/
109#[inline]
110fn j1f_asympt(x: f32) -> f64 {
111 static SGN: [f64; 2] = [1., -1.];
112 let sign_scale = SGN[x.is_sign_negative() as usize];
113 let x = f32::from_bits(x.to_bits() & 0x7fff_ffff);
114
115 let dx = x as f64;
116
117 let alpha = j1f_asympt_alpha(dx);
118 let beta = j1f_asympt_beta(dx);
119
120 let angle = rem2pif_any(x);
121
122 const SQRT_2_OVER_PI: f64 = f64::from_bits(0x3fe9884533d43651);
123 const MPI_OVER_4: f64 = f64::from_bits(0xbfe921fb54442d18);
124
125 let x0pi34 = MPI_OVER_4 - alpha;
126 let r0 = angle + x0pi34;
127
128 let m_sin = sin_small(r0);
129
130 let z0 = beta * m_sin;
131 let scale = SQRT_2_OVER_PI * j1f_rsqrt(dx);
132
133 scale * z0 * sign_scale
134}
135
136/**
137Note expansion generation below: this is negative series expressed in Sage as positive,
138so before any real evaluation `x=1/x` should be applied.
139
140Generated by SageMath:
141```python
142def binomial_like(n, m):
143 prod = QQ(1)
144 z = QQ(4)*(n**2)
145 for k in range(1,m + 1):
146 prod *= (z - (2*k - 1)**2)
147 return prod / (QQ(2)**(2*m) * (ZZ(m).factorial()))
148
149R = LaurentSeriesRing(RealField(300), 'x',default_prec=300)
150x = R.gen()
151
152def Pn_asymptotic(n, y, terms=10):
153 # now y = 1/x
154 return sum( (-1)**m * binomial_like(n, 2*m) / (QQ(2)**(2*m)) * y**(QQ(2)*m) for m in range(terms) )
155
156def Qn_asymptotic(n, y, terms=10):
157 return sum( (-1)**m * binomial_like(n, 2*m + 1) / (QQ(2)**(2*m + 1)) * y**(QQ(2)*m + 1) for m in range(terms) )
158
159P = Pn_asymptotic(1, x, 50)
160Q = Qn_asymptotic(1, x, 50)
161
162R_series = (-Q/P)
163
164# alpha is atan(R_series) so we're doing Taylor series atan expansion on R_series
165
166arctan_series_Z = sum([QQ(-1)**k * x**(QQ(2)*k+1) / RealField(700)(RealField(700)(2)*k+1) for k in range(25)])
167alpha_series = arctan_series_Z(R_series)
168
169# see the series
170print(alpha_series)
171```
172
173See notes/bessel_asympt.ipynb for generation
174**/
175#[inline]
176pub(crate) fn j1f_asympt_alpha(x: f64) -> f64 {
177 const C: [u64; 12] = [
178 0xbfd8000000000000,
179 0x3fc5000000000000,
180 0xbfd7bccccccccccd,
181 0x4002f486db6db6db,
182 0xc03e9fbf40000000,
183 0x4084997b55945d17,
184 0xc0d4a914195269d9,
185 0x412cd1b53816aec1,
186 0xc18aa4095d419351,
187 0x41ef809305f11b9d,
188 0xc2572e6809ed618b,
189 0x42c4c5b6057839f9,
190 ];
191 let recip = 1. / x;
192 let x2 = recip * recip;
193 let p = f_polyeval12(
194 x2,
195 f64::from_bits(C[0]),
196 f64::from_bits(C[1]),
197 f64::from_bits(C[2]),
198 f64::from_bits(C[3]),
199 f64::from_bits(C[4]),
200 f64::from_bits(C[5]),
201 f64::from_bits(C[6]),
202 f64::from_bits(C[7]),
203 f64::from_bits(C[8]),
204 f64::from_bits(C[9]),
205 f64::from_bits(C[10]),
206 f64::from_bits(C[11]),
207 );
208 p * recip
209}
210
211/**
212Note expansion generation below: this is negative series expressed in Sage as positive,
213so before any real evaluation `x=1/x` should be applied
214
215Generated by SageMath:
216```python
217def binomial_like(n, m):
218 prod = QQ(1)
219 z = QQ(4)*(n**2)
220 for k in range(1,m + 1):
221 prod *= (z - (2*k - 1)**2)
222 return prod / (QQ(2)**(2*m) * (ZZ(m).factorial()))
223
224R = LaurentSeriesRing(RealField(300), 'x',default_prec=300)
225x = R.gen()
226
227def Pn_asymptotic(n, y, terms=10):
228 # now y = 1/x
229 return sum( (-1)**m * binomial_like(n, 2*m) / (QQ(2)**(2*m)) * y**(QQ(2)*m) for m in range(terms) )
230
231def Qn_asymptotic(n, y, terms=10):
232 return sum( (-1)**m * binomial_like(n, 2*m + 1) / (QQ(2)**(2*m + 1)) * y**(QQ(2)*m + 1) for m in range(terms) )
233
234P = Pn_asymptotic(1, x, 50)
235Q = Qn_asymptotic(1, x, 50)
236
237def sqrt_series(s):
238 val = S.valuation()
239 lc = S[val] # Leading coefficient
240 b = lc.sqrt() * x**(val // 2)
241
242 for _ in range(5):
243 b = (b + S / b) / 2
244 b = b
245 return b
246
247S = (P**2 + Q**2).truncate(50)
248
249b_series = sqrt_series(S).truncate(30)
250# see the beta series
251print(b_series)
252```
253
254See notes/bessel_asympt.ipynb for generation
255**/
256#[inline]
257pub(crate) fn j1f_asympt_beta(x: f64) -> f64 {
258 const C: [u64; 10] = [
259 0x3ff0000000000000,
260 0x3fc8000000000000,
261 0xbfc8c00000000000,
262 0x3fe9c50000000000,
263 0xc01ef5b680000000,
264 0x40609860dd400000,
265 0xc0abae9b7a06e000,
266 0x41008711d41c1428,
267 0xc15ab70164c8be6e,
268 0x41bc1055e24f297f,
269 ];
270 let recip = 1. / x;
271 let x2 = recip * recip;
272 f_polyeval10(
273 x2,
274 f64::from_bits(C[0]),
275 f64::from_bits(C[1]),
276 f64::from_bits(C[2]),
277 f64::from_bits(C[3]),
278 f64::from_bits(C[4]),
279 f64::from_bits(C[5]),
280 f64::from_bits(C[6]),
281 f64::from_bits(C[7]),
282 f64::from_bits(C[8]),
283 f64::from_bits(C[9]),
284 )
285}
286
287/**
288Generated in Sollya:
289```python
290pretty = proc(u) {
291 return ~(floor(u*1000)/1000);
292};
293
294bessel_j1 = library("./cmake-build-release/libbessel_sollya.dylib");
295
296f = bessel_j1(x)/x;
297d = [0, 0.921];
298w = 1;
299pf = fpminimax(f, [|0,2,4,6,8,10,12|], [|D...|], d, absolute, floating);
300
301w = 1;
302or_f = bessel_j1(x);
303pf1 = pf * x;
304err_p = -log2(dirtyinfnorm(pf1*w-or_f, d));
305print ("relative error:", pretty(err_p));
306
307for i from 0 to degree(pf) by 2 do {
308 print("'", coeff(pf, i), "',");
309};
310```
311See ./notes/bessel_sollya/bessel_j1f_at_zero.sollya
312**/
313#[inline]
314fn poly_near_zero(x: f32) -> f32 {
315 let dx = x as f64;
316 let x2 = dx * dx;
317 let p = f_polyeval7(
318 x2,
319 f64::from_bits(0x3fe0000000000000),
320 f64::from_bits(0xbfaffffffffffffc),
321 f64::from_bits(0x3f65555555554089),
322 f64::from_bits(0xbf0c71c71c2a74ae),
323 f64::from_bits(0x3ea6c16bbd1dc5c1),
324 f64::from_bits(0xbe384562afb69e7d),
325 f64::from_bits(0x3dc248d0d0221cd0),
326 );
327 (p * dx) as f32
328}
329
330/// This method on small range searches for nearest zero or extremum.
331/// Then picks stored series expansion at the point end evaluates the poly at the point.
332#[inline]
333fn small_argument_path(x: f32) -> f32 {
334 static SIGN: [f64; 2] = [1., -1.];
335 let sign_scale = SIGN[x.is_sign_negative() as usize];
336 let x_abs = f32::from_bits(x.to_bits() & 0x7fff_ffff) as f64;
337
338 // let avg_step = 74.60109 / 47.0;
339 // let inv_step = 1.0 / avg_step;
340
341 const INV_STEP: f64 = 0.6300176043004198;
342
343 let fx = x_abs * INV_STEP;
344 const J1_ZEROS_COUNT: f64 = (J1_ZEROS.len() - 1) as f64;
345 let idx0 = unsafe { fx.min(J1_ZEROS_COUNT).to_int_unchecked::<usize>() };
346 let idx1 = unsafe { fx.ceil().min(J1_ZEROS_COUNT).to_int_unchecked::<usize>() };
347
348 let found_zero0 = DoubleDouble::from_bit_pair(J1_ZEROS[idx0]);
349 let found_zero1 = DoubleDouble::from_bit_pair(J1_ZEROS[idx1]);
350
351 let dist0 = (found_zero0.hi - x_abs).abs();
352 let dist1 = (found_zero1.hi - x_abs).abs();
353
354 let (found_zero, idx, dist) = if dist0 < dist1 {
355 (found_zero0, idx0, dist0)
356 } else {
357 (found_zero1, idx1, dist1)
358 };
359
360 if idx == 0 {
361 return poly_near_zero(x);
362 }
363
364 // We hit exact zero, value, better to return it directly
365 if dist == 0. {
366 return (f64::from_bits(J1_ZEROS_VALUE[idx]) * sign_scale) as f32;
367 }
368
369 let c = &J1F_COEFFS[idx - 1];
370
371 let r = (x_abs - found_zero.hi) - found_zero.lo;
372
373 let p = f_polyeval14(
374 r,
375 f64::from_bits(c[0]),
376 f64::from_bits(c[1]),
377 f64::from_bits(c[2]),
378 f64::from_bits(c[3]),
379 f64::from_bits(c[4]),
380 f64::from_bits(c[5]),
381 f64::from_bits(c[6]),
382 f64::from_bits(c[7]),
383 f64::from_bits(c[8]),
384 f64::from_bits(c[9]),
385 f64::from_bits(c[10]),
386 f64::from_bits(c[11]),
387 f64::from_bits(c[12]),
388 f64::from_bits(c[13]),
389 );
390
391 (p * sign_scale) as f32
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397
398 #[test]
399 fn test_f_j1f() {
400 assert_eq!(
401 f_j1f(77.743162408196766932633181568235159),
402 0.09049267898021947
403 );
404 assert_eq!(
405 f_j1f(-0.000000000000000000000000000000000000008827127),
406 -0.0000000000000000000000000000000000000044135635
407 );
408 assert_eq!(
409 f_j1f(0.000000000000000000000000000000000000008827127),
410 0.0000000000000000000000000000000000000044135635
411 );
412 assert_eq!(f_j1f(5.4), -0.3453447907795863);
413 assert_eq!(
414 f_j1f(84.027189586293545175976760219782591),
415 0.0870430264022591
416 );
417 assert_eq!(f_j1f(f32::INFINITY), 0.);
418 assert_eq!(f_j1f(f32::NEG_INFINITY), 0.);
419 assert!(f_j1f(f32::NAN).is_nan());
420 assert_eq!(f_j1f(-1.7014118e38), 0.000000000000000000006856925);
421 }
422}