Coverage Report

Created: 2025-12-20 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/pxfm-0.1.27/src/bessel/jincpi.rs
Line
Count
Source
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
 */
29
30
#![allow(clippy::excessive_precision)]
31
32
use crate::bessel::alpha1::bessel_1_asympt_alpha_fast;
33
use crate::bessel::beta1::bessel_1_asympt_beta_fast;
34
use crate::bessel::j1_coeffs::{J1_COEFFS, J1_ZEROS, J1_ZEROS_VALUE};
35
use crate::bessel::j1_coeffs_taylor::J1_COEFFS_TAYLOR;
36
use crate::common::f_fmla;
37
use crate::double_double::DoubleDouble;
38
use crate::polyeval::{f_polyeval9, f_polyeval19};
39
use crate::rounding::CpuCeil;
40
use crate::rounding::CpuRound;
41
use crate::sin_helper::sin_dd_small_fast;
42
43
/// Normalized jinc 2*J1(PI\*x)/(pi\*x)
44
0
pub fn f_jincpi(x: f64) -> f64 {
45
0
    let ux = x.to_bits().wrapping_shl(1);
46
47
0
    if ux >= 0x7ffu64 << 53 || ux <= 0x7960000000000000u64 {
48
        // |x| <= f64::EPSILON, |x| == inf, x == NaN
49
0
        if ux <= 0x7960000000000000u64 {
50
            // |x| <= f64::EPSILON
51
0
            return 1.0;
52
0
        }
53
0
        if x.is_infinite() {
54
0
            return 0.;
55
0
        }
56
0
        return x + f64::NAN; // x = NaN
57
0
    }
58
59
0
    let ax: u64 = x.to_bits() & 0x7fff_ffff_ffff_ffff;
60
61
0
    if ax < 0x4052a6784230fcf8u64 {
62
        // |x| < 74.60109
63
0
        if ax < 0x3fd3333333333333 {
64
            // |x| < 0.3
65
0
            return jincpi_near_zero(f64::from_bits(ax));
66
0
        }
67
0
        let scaled_pix = f64::from_bits(ax) * std::f64::consts::PI; // just test boundaries
68
0
        if scaled_pix < 74.60109 {
69
0
            return jinc_small_argument_fast(f64::from_bits(ax));
70
0
        }
71
0
    }
72
73
0
    jinc_asympt_fast(f64::from_bits(ax))
74
0
}
75
76
/*
77
   Evaluates:
78
   J1 = sqrt(2/(PI*x)) * beta(x) * cos(x - 3*PI/4 - alpha(x))
79
   discarding 1*PI/2 using identities gives:
80
   J1 = sqrt(2/(PI*x)) * beta(x) * sin(x - PI/4 - alpha(x))
81
82
   to avoid squashing small (-PI/4 - alpha(x)) into a large x actual expansion is:
83
84
   J1 = sqrt(2/(PI*x)) * beta(x) * sin((x mod 2*PI) - PI/4 - alpha(x))
85
*/
86
#[inline]
87
0
fn jinc_asympt_fast(ox: f64) -> f64 {
88
    const PI: DoubleDouble = DoubleDouble::new(
89
        f64::from_bits(0x3ca1a62633145c07),
90
        f64::from_bits(0x400921fb54442d18),
91
    );
92
93
0
    let px = DoubleDouble::quick_mult_f64(PI, ox);
94
95
    // 2^(3/2)/(Pi^2)
96
    // reduce argument 2*sqrt(2/(PI*(x*PI))) = 2*sqrt(2)/PI
97
    // adding additional pi from division then 2*sqrt(2)/PI^2
98
    const Z2_3_2_OVER_PI_SQR: DoubleDouble =
99
        DoubleDouble::from_bit_pair((0xbc76213a285b8094, 0x3fd25751e5614413));
100
    const MPI_OVER_4: DoubleDouble = DoubleDouble::new(
101
        f64::from_bits(0xbc81a62633145c07),
102
        f64::from_bits(0xbfe921fb54442d18),
103
    );
104
105
    // argument reduction assuming x here value is already multiple of PI.
106
    // k = round((x*Pi) / (pi*2))
107
0
    let kd = (ox * 0.5).cpu_round();
108
    //  y = (x * Pi) - k * 2
109
0
    let rem = f_fmla(kd, -2., ox);
110
0
    let angle = DoubleDouble::quick_mult_f64(PI, rem);
111
112
0
    let recip = px.recip();
113
114
0
    let alpha = bessel_1_asympt_alpha_fast(recip);
115
0
    let beta = bessel_1_asympt_beta_fast(recip);
116
117
    // Without full subtraction cancellation happens sometimes
118
0
    let x0pi34 = DoubleDouble::full_dd_sub(MPI_OVER_4, alpha);
119
0
    let r0 = DoubleDouble::full_dd_add(angle, x0pi34);
120
121
0
    let m_sin = sin_dd_small_fast(r0);
122
0
    let z0 = DoubleDouble::quick_mult(beta, m_sin);
123
0
    let ox_recip = DoubleDouble::from_quick_recip(ox);
124
0
    let dx_sqrt = ox_recip.fast_sqrt();
125
0
    let scale = DoubleDouble::quick_mult(Z2_3_2_OVER_PI_SQR, dx_sqrt);
126
0
    let p = DoubleDouble::quick_mult(scale, z0);
127
128
0
    DoubleDouble::quick_mult(p, ox_recip).to_f64()
129
0
}
130
131
#[inline]
132
0
pub(crate) fn jincpi_near_zero(x: f64) -> f64 {
133
    // Polynomial Generated by Wolfram Mathematica:
134
    // <<FunctionApproximations`
135
    // ClearAll["Global`*"]
136
    // f[x_]:=2*BesselJ[1,x*Pi]/(x*Pi)
137
    // {err,approx}=MiniMaxApproximation[f[z],{z,{2^-23,0.3},7,7},WorkingPrecision->60]
138
    // poly=Numerator[approx][[1]];
139
    // coeffs=CoefficientList[poly,z];
140
    // TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50},ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
141
    // poly=Denominator[approx][[1]];
142
    // coeffs=CoefficientList[poly,z];
143
    // TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50},ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
144
    const P: [(u64, u64); 8] = [
145
        (0xbb3bddffe9450ca6, 0x3ff0000000000000),
146
        (0x3c4b0b0a7393eccb, 0xbfde4cd3c3c87615),
147
        (0xbc8f9f784e0594a6, 0xbff043283b1e383f),
148
        (0xbc7af77bca466875, 0x3fdee46673cf919f),
149
        (0xbc1b62837b038ea8, 0x3fd0b7cc55c9a4af),
150
        (0x3c6c08841871f124, 0xbfc002b65231dcdd),
151
        (0xbc36cf2d89ea63bc, 0xbf949022a7a0712b),
152
        (0xbbf535d492c0ac1c, 0x3f840b48910d5105),
153
    ];
154
155
    const Q: [(u64, u64); 8] = [
156
        (0x0000000000000000, 0x3ff0000000000000),
157
        (0x3c4aba6577f3253e, 0xbfde4cd3c3c87615),
158
        (0x3c52f58f82e3438c, 0x3fcbd0a475006cf9),
159
        (0x3c36e496237d6b49, 0xbfb9f4cea13b06e9),
160
        (0xbbbbf3e4ef3a28fe, 0x3f967ed0cee85392),
161
        (0x3c267ac442bb3bcf, 0xbf846e192e22f862),
162
        (0x3bd84e9888993cb0, 0x3f51e0fff3cfddee),
163
        (0x3bd7c0285797bd8e, 0xbf3ea7a621fa1c8c),
164
    ];
165
166
0
    let x2 = DoubleDouble::from_exact_mult(x, x);
167
0
    let x4 = x2 * x2;
168
169
0
    let p0 = DoubleDouble::mul_f64_add(
170
0
        DoubleDouble::from_bit_pair(P[1]),
171
0
        x,
172
0
        DoubleDouble::from_bit_pair(P[0]),
173
    );
174
0
    let p1 = DoubleDouble::mul_f64_add(
175
0
        DoubleDouble::from_bit_pair(P[3]),
176
0
        x,
177
0
        DoubleDouble::from_bit_pair(P[2]),
178
    );
179
0
    let p2 = DoubleDouble::mul_f64_add(
180
0
        DoubleDouble::from_bit_pair(P[5]),
181
0
        x,
182
0
        DoubleDouble::from_bit_pair(P[4]),
183
    );
184
0
    let p3 = DoubleDouble::mul_f64_add(
185
0
        DoubleDouble::from_bit_pair(P[7]),
186
0
        x,
187
0
        DoubleDouble::from_bit_pair(P[6]),
188
    );
189
190
0
    let q0 = DoubleDouble::mul_add(x2, p1, p0);
191
0
    let q1 = DoubleDouble::mul_add(x2, p3, p2);
192
193
0
    let p_num = DoubleDouble::mul_add(x4, q1, q0);
194
195
0
    let p0 = DoubleDouble::mul_f64_add(
196
0
        DoubleDouble::from_bit_pair(Q[1]),
197
0
        x,
198
0
        DoubleDouble::from_bit_pair(Q[0]),
199
    );
200
0
    let p1 = DoubleDouble::mul_f64_add(
201
0
        DoubleDouble::from_bit_pair(Q[3]),
202
0
        x,
203
0
        DoubleDouble::from_bit_pair(Q[2]),
204
    );
205
0
    let p2 = DoubleDouble::mul_f64_add(
206
0
        DoubleDouble::from_bit_pair(Q[5]),
207
0
        x,
208
0
        DoubleDouble::from_bit_pair(Q[4]),
209
    );
210
0
    let p3 = DoubleDouble::mul_f64_add(
211
0
        DoubleDouble::from_bit_pair(Q[7]),
212
0
        x,
213
0
        DoubleDouble::from_bit_pair(Q[6]),
214
    );
215
216
0
    let q0 = DoubleDouble::mul_add(x2, p1, p0);
217
0
    let q1 = DoubleDouble::mul_add(x2, p3, p2);
218
219
0
    let p_den = DoubleDouble::mul_add(x4, q1, q0);
220
221
0
    DoubleDouble::div(p_num, p_den).to_f64()
222
0
}
223
224
/// This method on small range searches for nearest zero or extremum.
225
/// Then picks stored series expansion at the point end evaluates the poly at the point.
226
#[inline]
227
0
pub(crate) fn jinc_small_argument_fast(x: f64) -> f64 {
228
    const PI: DoubleDouble = DoubleDouble::new(
229
        f64::from_bits(0x3ca1a62633145c07),
230
        f64::from_bits(0x400921fb54442d18),
231
    );
232
233
    // let avg_step = 74.60109 / 47.0;
234
    // let inv_step = 1.0 / avg_step;
235
236
0
    let dx = DoubleDouble::quick_mult_f64(PI, x);
237
238
    const INV_STEP: f64 = 0.6300176043004198;
239
240
0
    let fx = dx.hi * INV_STEP;
241
    const J1_ZEROS_COUNT: f64 = (J1_ZEROS.len() - 1) as f64;
242
0
    let idx0 = unsafe { fx.min(J1_ZEROS_COUNT).to_int_unchecked::<usize>() };
243
0
    let idx1 = unsafe {
244
0
        fx.cpu_ceil()
245
0
            .min(J1_ZEROS_COUNT)
246
0
            .to_int_unchecked::<usize>()
247
    };
248
249
0
    let found_zero0 = DoubleDouble::from_bit_pair(J1_ZEROS[idx0]);
250
0
    let found_zero1 = DoubleDouble::from_bit_pair(J1_ZEROS[idx1]);
251
252
0
    let dist0 = (found_zero0.hi - dx.hi).abs();
253
0
    let dist1 = (found_zero1.hi - dx.hi).abs();
254
255
0
    let (found_zero, idx, dist) = if dist0 < dist1 {
256
0
        (found_zero0, idx0, dist0)
257
    } else {
258
0
        (found_zero1, idx1, dist1)
259
    };
260
261
0
    if idx == 0 {
262
0
        return jincpi_near_zero(x);
263
0
    }
264
265
0
    let r = DoubleDouble::quick_dd_sub(dx, found_zero);
266
267
    // We hit exact zero, value, better to return it directly
268
0
    if dist == 0. {
269
0
        return DoubleDouble::quick_mult_f64(
270
0
            DoubleDouble::from_f64_div_dd(f64::from_bits(J1_ZEROS_VALUE[idx]), dx),
271
            2.,
272
        )
273
0
        .to_f64();
274
0
    }
275
276
0
    let is_zero_too_close = dist.abs() < 1e-3;
277
278
0
    let c = if is_zero_too_close {
279
0
        &J1_COEFFS_TAYLOR[idx - 1]
280
    } else {
281
0
        &J1_COEFFS[idx - 1]
282
    };
283
284
0
    let p = f_polyeval19(
285
0
        r.hi,
286
0
        f64::from_bits(c[5].1),
287
0
        f64::from_bits(c[6].1),
288
0
        f64::from_bits(c[7].1),
289
0
        f64::from_bits(c[8].1),
290
0
        f64::from_bits(c[9].1),
291
0
        f64::from_bits(c[10].1),
292
0
        f64::from_bits(c[11].1),
293
0
        f64::from_bits(c[12].1),
294
0
        f64::from_bits(c[13].1),
295
0
        f64::from_bits(c[14].1),
296
0
        f64::from_bits(c[15].1),
297
0
        f64::from_bits(c[16].1),
298
0
        f64::from_bits(c[17].1),
299
0
        f64::from_bits(c[18].1),
300
0
        f64::from_bits(c[19].1),
301
0
        f64::from_bits(c[20].1),
302
0
        f64::from_bits(c[21].1),
303
0
        f64::from_bits(c[22].1),
304
0
        f64::from_bits(c[23].1),
305
    );
306
307
0
    let mut z = DoubleDouble::mul_f64_add(r, p, DoubleDouble::from_bit_pair(c[4]));
308
0
    z = DoubleDouble::mul_add(z, r, DoubleDouble::from_bit_pair(c[3]));
309
0
    z = DoubleDouble::mul_add(z, r, DoubleDouble::from_bit_pair(c[2]));
310
0
    z = DoubleDouble::mul_add(z, r, DoubleDouble::from_bit_pair(c[1]));
311
0
    z = DoubleDouble::mul_add(z, r, DoubleDouble::from_bit_pair(c[0]));
312
313
0
    z = DoubleDouble::div(z, dx);
314
0
    z.hi *= 2.;
315
0
    z.lo *= 2.;
316
317
0
    let err = f_fmla(
318
0
        z.hi,
319
0
        f64::from_bits(0x3c70000000000000), // 2^-56
320
0
        f64::from_bits(0x3bf0000000000000), // 2^-64
321
    );
322
0
    let ub = z.hi + (z.lo + err);
323
0
    let lb = z.hi + (z.lo - err);
324
325
0
    if ub == lb {
326
0
        return z.to_f64();
327
0
    }
328
329
0
    j1_small_argument_dd(r, c, dx)
330
0
}
331
332
0
fn j1_small_argument_dd(r: DoubleDouble, c0: &[(u64, u64); 24], inv_scale: DoubleDouble) -> f64 {
333
0
    let c = &c0[15..];
334
335
0
    let p0 = f_polyeval9(
336
0
        r.to_f64(),
337
0
        f64::from_bits(c[0].1),
338
0
        f64::from_bits(c[1].1),
339
0
        f64::from_bits(c[2].1),
340
0
        f64::from_bits(c[3].1),
341
0
        f64::from_bits(c[4].1),
342
0
        f64::from_bits(c[5].1),
343
0
        f64::from_bits(c[6].1),
344
0
        f64::from_bits(c[7].1),
345
0
        f64::from_bits(c[8].1),
346
    );
347
348
0
    let c = c0;
349
350
0
    let mut p_e = DoubleDouble::mul_f64_add(r, p0, DoubleDouble::from_bit_pair(c[14]));
351
0
    p_e = DoubleDouble::mul_add(p_e, r, DoubleDouble::from_bit_pair(c[13]));
352
0
    p_e = DoubleDouble::mul_add(p_e, r, DoubleDouble::from_bit_pair(c[12]));
353
0
    p_e = DoubleDouble::mul_add(p_e, r, DoubleDouble::from_bit_pair(c[11]));
354
0
    p_e = DoubleDouble::mul_add(p_e, r, DoubleDouble::from_bit_pair(c[10]));
355
0
    p_e = DoubleDouble::mul_add(p_e, r, DoubleDouble::from_bit_pair(c[9]));
356
0
    p_e = DoubleDouble::mul_add(p_e, r, DoubleDouble::from_bit_pair(c[8]));
357
0
    p_e = DoubleDouble::mul_add(p_e, r, DoubleDouble::from_bit_pair(c[7]));
358
0
    p_e = DoubleDouble::mul_add(p_e, r, DoubleDouble::from_bit_pair(c[6]));
359
0
    p_e = DoubleDouble::mul_add(p_e, r, DoubleDouble::from_bit_pair(c[5]));
360
0
    p_e = DoubleDouble::mul_add(p_e, r, DoubleDouble::from_bit_pair(c[4]));
361
0
    p_e = DoubleDouble::mul_add(p_e, r, DoubleDouble::from_bit_pair(c[3]));
362
0
    p_e = DoubleDouble::mul_add(p_e, r, DoubleDouble::from_bit_pair(c[2]));
363
0
    p_e = DoubleDouble::mul_add(p_e, r, DoubleDouble::from_bit_pair(c[1]));
364
0
    p_e = DoubleDouble::mul_add(p_e, r, DoubleDouble::from_bit_pair(c[0]));
365
366
0
    let p = DoubleDouble::from_exact_add(p_e.hi, p_e.lo);
367
0
    let mut z = DoubleDouble::div(p, inv_scale);
368
0
    z.hi *= 2.;
369
0
    z.lo *= 2.;
370
0
    z.to_f64()
371
0
}
372
373
#[cfg(test)]
374
mod tests {
375
    use super::*;
376
377
    #[test]
378
    fn test_jincpi() {
379
        assert_eq!(f_jincpi(f64::EPSILON), 1.0);
380
        assert_eq!(f_jincpi(0.000043242121), 0.9999999976931268);
381
        assert_eq!(f_jincpi(0.5000000000020244), 0.7217028449014163);
382
        assert_eq!(f_jincpi(73.81695991658546), -0.0004417546638317049);
383
        assert_eq!(f_jincpi(0.01), 0.9998766350182722);
384
        assert_eq!(f_jincpi(0.9), 0.28331697846510623);
385
        assert_eq!(f_jincpi(3.831705970207517), -0.036684415010255086);
386
        assert_eq!(f_jincpi(-3.831705970207517), -0.036684415010255086);
387
        assert_eq!(
388
            f_jincpi(0.000000000000000000000000000000000000008827127),
389
            1.0
390
        );
391
        assert_eq!(
392
            f_jincpi(-0.000000000000000000000000000000000000008827127),
393
            1.0
394
        );
395
        assert_eq!(f_jincpi(5.4), -0.010821736808448256);
396
        assert_eq!(
397
            f_jincpi(77.743162408196766932633181568235159),
398
            -0.00041799098646950523
399
        );
400
        assert_eq!(
401
            f_jincpi(84.027189586293545175976760219782591),
402
            -0.00023927934929850555
403
        );
404
        assert_eq!(f_jincpi(f64::NEG_INFINITY), 0.0);
405
        assert_eq!(f_jincpi(f64::INFINITY), 0.0);
406
        assert!(f_jincpi(f64::NAN).is_nan());
407
    }
408
}