use super::{PI, SQRT_PI};
pub(crate) const TWO_M27: f64 = 7.4505805969238281250000000000000000000000000000000e-9; pub(crate) const TWO_M13: f64 = 0.00012207031250000000000000000000000000000000000000000; pub(crate) const TWO_129: f64 = 6.8056473384187692692674921486353642291200000000000e38;
const R02: f64 = 1.56249999999999947958e-02; const R03: f64 = -1.89979294238854721751e-04; const R04: f64 = 1.82954049532700665670e-06; const R05: f64 = -4.61832688532103189199e-09; const S01: f64 = 1.56191029464890010492e-02; const S02: f64 = 1.16926784663337450260e-04; const S03: f64 = 5.13546550207318111446e-07; const S04: f64 = 1.16614003333790000205e-09;
pub fn bessel_j0(x: f64) -> f64 {
if f64::is_nan(x) {
return f64::NAN;
} else if f64::is_infinite(x) {
return 0.0;
} else if x == 0.0 {
return 1.0;
}
let x = f64::abs(x);
if x >= 2.0 {
let (s, c) = f64::sin_cos(x);
let mut ss = s - c;
let mut cc = s + c;
if x < f64::MAX / 2.0 {
let z = -f64::cos(x + x);
if s * c < 0.0 {
cc = z / ss;
} else {
ss = z / cc;
}
}
let z = if x > TWO_129 {
(1.0 / SQRT_PI) * cc / f64::sqrt(x)
} else {
let u = pzero(x);
let v = qzero(x);
(1.0 / SQRT_PI) * (u * cc - v * ss) / f64::sqrt(x)
};
return z;
}
if x < TWO_M13 {
if x < TWO_M27 {
return 1.0;
}
return 1.0 - 0.25 * x * x;
}
let z = x * x;
let r = z * (R02 + z * (R03 + z * (R04 + z * R05)));
let s = 1.0 + z * (S01 + z * (S02 + z * (S03 + z * S04)));
if x < 1.0 {
return 1.0 + z * (-0.25 + (r / s));
}
let u = 0.5 * x;
(1.0 + u) * (1.0 - u) + z * (r / s)
}
const U00: f64 = -7.38042951086872317523e-02; const U01: f64 = 1.76666452509181115538e-01; const U02: f64 = -1.38185671945596898896e-02; const U03: f64 = 3.47453432093683650238e-04; const U04: f64 = -3.81407053724364161125e-06; const U05: f64 = 1.95590137035022920206e-08; const U06: f64 = -3.98205194132103398453e-11; const V01: f64 = 1.27304834834123699328e-02; const V02: f64 = 7.60068627350353253702e-05; const V03: f64 = 2.59150851840457805467e-07; const V04: f64 = 4.41110311332675467403e-10;
pub fn bessel_y0(x: f64) -> f64 {
if x < 0.0 || f64::is_nan(x) {
return f64::NAN;
} else if f64::is_infinite(x) {
return 0.0;
} else if x == 0.0 {
return f64::NEG_INFINITY;
}
if x >= 2.0 {
let (s, c) = f64::sin_cos(x);
let mut ss = s - c;
let mut cc = s + c;
if x < f64::MAX / 2.0 {
let z = -f64::cos(x + x);
if s * c < 0.0 {
cc = z / ss;
} else {
ss = z / cc;
}
}
let z = if x > TWO_129 {
(1.0 / SQRT_PI) * ss / f64::sqrt(x)
} else {
let u = pzero(x);
let v = qzero(x);
(1.0 / SQRT_PI) * (u * ss + v * cc) / f64::sqrt(x)
};
return z;
}
if x <= TWO_M27 {
return U00 + (2.0 / PI) * f64::ln(x); }
let z = x * x;
let u = U00 + z * (U01 + z * (U02 + z * (U03 + z * (U04 + z * (U05 + z * U06)))));
let v = 1.0 + z * (V01 + z * (V02 + z * (V03 + z * V04)));
u / v + (2.0 / PI) * bessel_j0(x) * f64::ln(x) }
const P0R8: [f64; 6] = [
0.00000000000000000000e+00, -7.03124999999900357484e-02, -8.08167041275349795626e+00, -2.57063105679704847262e+02, -2.48521641009428822144e+03, -5.25304380490729545272e+03, ];
const P0S8: [f64; 5] = [
1.16534364619668181717e+02, 3.83374475364121826715e+03, 4.05978572648472545552e+04, 1.16752972564375915681e+05, 4.76277284146730962675e+04, ];
const P0R5: [f64; 6] = [
-1.14125464691894502584e-11, -7.03124940873599280078e-02, -4.15961064470587782438e+00, -6.76747652265167261021e+01, -3.31231299649172967747e+02, -3.46433388365604912451e+02, ];
const P0S5: [f64; 5] = [
6.07539382692300335975e+01, 1.05125230595704579173e+03, 5.97897094333855784498e+03, 9.62544514357774460223e+03, 2.40605815922939109441e+03, ];
const P0R3: [f64; 6] = [
-2.54704601771951915620e-09, -7.03119616381481654654e-02, -2.40903221549529611423e+00, -2.19659774734883086467e+01, -5.80791704701737572236e+01, -3.14479470594888503854e+01, ];
const P0S3: [f64; 5] = [
3.58560338055209726349e+01, 3.61513983050303863820e+02, 1.19360783792111533330e+03, 1.12799679856907414432e+03, 1.73580930813335754692e+02, ];
const P0R2: [f64; 6] = [
-8.87534333032526411254e-08, -7.03030995483624743247e-02, -1.45073846780952986357e+00, -7.63569613823527770791e+00, -1.11931668860356747786e+01, -3.23364579351335335033e+00, ];
const P0S2: [f64; 5] = [
2.22202997532088808441e+01, 1.36206794218215208048e+02, 2.70470278658083486789e+02, 1.53875394208320329881e+02, 1.46576176948256193810e+01, ];
fn pzero(x: f64) -> f64 {
let (p, q) = if x >= 8.0 {
(&P0R8, &P0S8)
} else if x >= 4.5454 {
(&P0R5, &P0S5)
} else if x >= 2.8571 {
(&P0R3, &P0S3)
} else if x >= 2.0 {
(&P0R2, &P0S2)
} else {
panic!("INTERNAL ERROR: x must be ≥ 2.0 for pzero");
};
let z = 1.0 / (x * x);
let r = p[0] + z * (p[1] + z * (p[2] + z * (p[3] + z * (p[4] + z * p[5]))));
let s = 1.0 + z * (q[0] + z * (q[1] + z * (q[2] + z * (q[3] + z * q[4]))));
1.0 + r / s
}
const Q0R8: [f64; 6] = [
0.00000000000000000000e+00, 7.32421874999935051953e-02, 1.17682064682252693899e+01, 5.57673380256401856059e+02, 8.85919720756468632317e+03, 3.70146267776887834771e+04, ];
const Q0S8: [f64; 6] = [
1.63776026895689824414e+02, 8.09834494656449805916e+03, 1.42538291419120476348e+05, 8.03309257119514397345e+05, 8.40501579819060512818e+05, -3.43899293537866615225e+05, ];
const Q0R5: [f64; 6] = [
1.84085963594515531381e-11, 7.32421766612684765896e-02, 5.83563508962056953777e+00, 1.35111577286449829671e+02, 1.02724376596164097464e+03, 1.98997785864605384631e+03, ];
const Q0S5: [f64; 6] = [
8.27766102236537761883e+01, 2.07781416421392987104e+03, 1.88472887785718085070e+04, 5.67511122894947329769e+04, 3.59767538425114471465e+04, -5.35434275601944773371e+03, ];
const Q0R3: [f64; 6] = [
4.37741014089738620906e-09, 7.32411180042911447163e-02, 3.34423137516170720929e+00, 4.26218440745412650017e+01, 1.70808091340565596283e+02, 1.66733948696651168575e+02, ];
const Q0S3: [f64; 6] = [
4.87588729724587182091e+01, 7.09689221056606015736e+02, 3.70414822620111362994e+03, 6.46042516752568917582e+03, 2.51633368920368957333e+03, -1.49247451836156386662e+02, ];
const Q0R2: [f64; 6] = [
1.50444444886983272379e-07, 7.32234265963079278272e-02, 1.99819174093815998816e+00, 1.44956029347885735348e+01, 3.16662317504781540833e+01, 1.62527075710929267416e+01, ];
const Q0S2: [f64; 6] = [
3.03655848355219184498e+01, 2.69348118608049844624e+02, 8.44783757595320139444e+02, 8.82935845112488550512e+02, 2.12666388511798828631e+02, -5.31095493882666946917e+00, ];
fn qzero(x: f64) -> f64 {
let (p, q) = if x >= 8.0 {
(&Q0R8, &Q0S8)
} else if x >= 4.5454 {
(&Q0R5, &Q0S5)
} else if x >= 2.8571 {
(&Q0R3, &Q0S3)
} else if x >= 2.0 {
(&Q0R2, &Q0S2)
} else {
panic!("INTERNAL ERROR: x must be ≥ 2.0 for qzero");
};
let z = 1.0 / (x * x);
let r = p[0] + z * (p[1] + z * (p[2] + z * (p[3] + z * (p[4] + z * p[5]))));
let s = 1.0 + z * (q[0] + z * (q[1] + z * (q[2] + z * (q[3] + z * (q[4] + z * q[5])))));
(-0.125 + r / s) / x
}
#[cfg(test)]
mod tests {
use super::{bessel_j0, bessel_y0, pzero, qzero, TWO_129};
use crate::{approx_eq, assert_alike};
#[test]
#[should_panic(expected = "INTERNAL ERROR: x must be ≥ 2.0 for pzero")]
fn pzero_panics_on_wrong_input() {
pzero(1.99);
}
#[test]
#[should_panic(expected = "INTERNAL ERROR: x must be ≥ 2.0 for qzero")]
fn qzero_panics_on_wrong_input() {
qzero(1.99);
}
#[test]
fn bessel_j0_handles_special_cases() {
assert!(bessel_j0(f64::NAN).is_nan());
assert_eq!(bessel_j0(f64::NEG_INFINITY), 0.0);
assert_eq!(bessel_j0(f64::INFINITY), 0.0);
assert_eq!(bessel_j0(0.0), 1.0);
}
#[test]
fn bessel_y0_handles_special_cases() {
assert!(bessel_y0(f64::NEG_INFINITY).is_nan());
assert!(bessel_y0(-0.01).is_nan());
assert!(bessel_y0(f64::NAN).is_nan());
assert_eq!(bessel_y0(f64::INFINITY), 0.0);
assert_eq!(bessel_y0(0.0), f64::NEG_INFINITY);
}
#[test]
fn bessel_j0_works() {
assert_eq!(
bessel_j0(-123.0),
-0.06854552119354654773723060960911865200289142665003329651340461580967205232587549262115495507220123533
);
assert_eq!(
bessel_j0(-5.0),
-0.1775967713143383043473970130747587110711303560085091289906582682081766005733631207589074141654715325
);
assert_eq!(
bessel_j0(-2.0),
0.2238907791412356680518274546499486258251544822186076031283497060108539577680107050148115118534293660
);
assert_eq!(
bessel_j0(-1.0),
0.7651976865579665514497175261026632209092742897553252418615475491192789122152724401671806000989156340
);
assert_eq!(
bessel_j0(1e-9),
0.99999999999999999975000000000000000001562499999999999999956597222222222222222900390624999999999993218
);
assert_eq!(
bessel_j0(1.0),
0.7651976865579665514497175261026632209092742897553252418615475491192789122152724401671806000989156340
);
assert_eq!(
bessel_j0(2.0),
0.2238907791412356680518274546499486258251544822186076031283497060108539577680107050148115118534293660
);
assert_eq!(
bessel_j0(5.0),
-0.1775967713143383043473970130747587110711303560085091289906582682081766005733631207589074141654715325
);
assert_eq!(
bessel_j0(123.0),
-0.06854552119354654773723060960911865200289142665003329651340461580967205232587549262115495507220123533
);
}
#[test]
fn bessel_y0_works() {
assert_eq!(
bessel_y0(1e-9),
-13.26664507493838655750870141542857521844493660738045065740801097731190898310202900407610027191316534
);
approx_eq(
bessel_y0(1.0),
0.08825696421567695798292676602351516282781752309067554671104384761199978932351337130107720035921993680,
1e-16,
);
assert_eq!(
bessel_y0(2.0),
0.5103756726497451195966065927271578732681392270858461355718392719327313950418217977114130750041427489
);
assert_eq!(
bessel_y0(5.0),
-0.3085176252490337800736489842120466113863470616273440437958514158982579156428808518901793164635184073
);
approx_eq(
bessel_y0(123.0),
0.02184580674805452669889249502284870517465956644502777321367441073587120026032506563177310919183224620,
1e-17,
);
}
#[test]
fn bessel_j0_edge_cases_work() {
let val = bessel_j0(f64::MAX / 2.0);
assert!(f64::abs(val) < 1e-153);
let val = bessel_j0(2.0 * TWO_129);
assert!(f64::abs(val) < 1e-20);
approx_eq(bessel_j0(0.5), 0.938469807240813, 1e-15);
}
#[test]
fn bessel_y0_edge_cases_work() {
let val = bessel_y0(f64::MAX / 2.0);
assert!(val > 0.0);
assert!(f64::abs(val) < 1e-154);
}
const VALUES: [f64; 10] = [
4.9790119248836735e+00,
7.7388724745781045e+00,
-2.7688005719200159e-01,
-5.0106036182710749e+00,
9.6362937071984173e+00,
2.9263772392439646e+00,
5.2290834314593066e+00,
2.7279399104360102e+00,
1.8253080916808550e+00,
-8.6859247685756013e+00,
];
const SOLUTION_J0: [f64; 10] = [
-1.8444682230601672018219338e-01,
2.27353668906331975435892e-01,
9.809259936157051116270273e-01,
-1.741170131426226587841181e-01,
-2.1389448451144143352039069e-01,
-2.340905848928038763337414e-01,
-1.0029099691890912094586326e-01,
-1.5466726714884328135358907e-01,
3.252650187653420388714693e-01,
-8.72218484409407250005360235e-03,
];
const SOLUTION_Y0: [f64; 10] = [
-3.053399153780788357534855e-01,
1.7437227649515231515503649e-01,
-8.6221781263678836910392572e-01,
-3.100664880987498407872839e-01,
1.422200649300982280645377e-01,
4.000004067997901144239363e-01,
-3.3340749753099352392332536e-01,
4.5399790746668954555205502e-01,
4.8290004112497761007536522e-01,
2.7036697826604756229601611e-01,
];
const SC_VALUES_J0: [f64; 4] = [f64::NEG_INFINITY, 0.0, f64::INFINITY, f64::NAN];
const SC_SOLUTION_J0: [f64; 4] = [0.0, 1.0, 0.0, f64::NAN];
const SC_VALUES_Y0: [f64; 5] = [f64::NEG_INFINITY, 0.0, f64::INFINITY, f64::NAN, -1.0];
const SC_SOLUTION_Y0: [f64; 5] = [f64::NAN, f64::NEG_INFINITY, 0.0, f64::NAN, f64::NAN];
#[test]
fn test_bessel_j0() {
for (i, v) in VALUES.iter().enumerate() {
let f = bessel_j0(*v);
approx_eq(SOLUTION_J0[i], f, 1e-16);
}
for (i, v) in SC_VALUES_J0.iter().enumerate() {
let f = bessel_j0(*v);
assert_alike(SC_SOLUTION_J0[i], f);
}
}
#[test]
fn test_bessel_y0() {
for (i, v) in VALUES.iter().enumerate() {
let a = f64::abs(*v);
let f = bessel_y0(a);
approx_eq(SOLUTION_Y0[i], f, 1e-15);
}
for (i, v) in SC_VALUES_Y0.iter().enumerate() {
let f = bessel_y0(*v);
assert_alike(SC_SOLUTION_Y0[i], f);
}
}
}