use num_traits::Float;
use crate::{
crate_util::{case, check, let_mut},
StrErr,
};
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
pub fn elliprc<T: Float>(x: T, y: T) -> Result<T, StrErr> {
if x < 0.0 {
return Err("elliprc: x must be non-negative.");
}
if y == 0.0 {
return Err("elliprc: y must be non-zero.");
}
let ans = elliprc_unchecked(x, y);
if ans.is_finite() {
return Ok(ans);
}
check!(@nan, elliprc, [x, y]);
case!(@any [x, y] == inf!(), T::zero());
Err("elliprc: Unexpected error.")
}
#[inline]
#[numeric_literals::replace_float_literals(T::from(literal).unwrap())]
pub fn elliprc_unchecked<T: Float>(x: T, y: T) -> T {
let_mut!(x, y);
let mut prefix = 1.0;
if y < 0.0 {
prefix = (x / (x - y)).sqrt();
x = x - y;
y = -y;
}
if x == 0.0 {
return prefix * pi_2!() / y.sqrt();
}
if x == y {
return prefix / x.sqrt();
}
if y > x {
return prefix * ((y - x) / x).sqrt().atan() / (y - x).sqrt();
}
#[cfg(feature = "test_force_fail")]
return nan!();
if y / x > 0.5 {
let arg = ((x - y) / x).sqrt();
prefix * ((arg).ln_1p() - (-arg).ln_1p()) / (2.0 * (x - y).sqrt())
} else {
prefix * ((x.sqrt() + (x - y).sqrt()) / y.sqrt()).ln() / (x - y).sqrt()
}
}
#[cfg(not(feature = "test_force_fail"))]
#[cfg(test)]
mod tests {
use super::*;
use crate::compare_test_data_boost;
fn _elliprc(inp: &[f64]) -> f64 {
elliprc(inp[0], inp[1]).unwrap()
}
#[test]
fn test_elliprc() {
compare_test_data_boost!("elliprc_data.txt", _elliprc, f64::EPSILON);
}
#[test]
fn test_elliprc_special_cases() {
use std::f64::{consts::PI, INFINITY, NAN};
assert_eq!(elliprc(-1.0, 1.0), Err("elliprc: x must be non-negative."));
assert_eq!(elliprc(1.0, 0.0), Err("elliprc: y must be non-zero."));
assert_eq!(elliprc(1.0, 1.0).unwrap(), 1.0);
assert_eq!(elliprc(4.0, 4.0).unwrap(), 0.5);
assert_eq!(elliprc(0.0, 1.0).unwrap(), PI / 2.0);
assert_eq!(elliprc(0.0, 4.0).unwrap(), PI / 4.0);
assert_eq!(elliprc(1.0, 4.0).unwrap(), (3.0.sqrt().atan() / 3.0.sqrt()));
assert_eq!(elliprc(1.0, 9.0).unwrap(), (8.0.sqrt().atan() / 8.0.sqrt()));
assert_eq!(
elliprc(4.0, 1.0).unwrap(),
((2.0 + 3.0.sqrt()).ln() / 3.0.sqrt())
);
assert_eq!(
elliprc(9.0, 1.0).unwrap(),
((3.0 + 8.0.sqrt()).ln() / 8.0.sqrt())
);
assert_eq!(elliprc(NAN, 1.0), Err("elliprc: Arguments cannot be NAN."));
assert_eq!(elliprc(1.0, NAN), Err("elliprc: Arguments cannot be NAN."));
assert_eq!(elliprc(INFINITY, 1.0).unwrap(), 0.0);
assert_eq!(elliprc(1.0, INFINITY).unwrap(), 0.0);
}
}
#[cfg(feature = "test_force_fail")]
crate::test_force_unreachable! {
assert_eq!(elliprc(2.0, 1.0), Err("elliprc: Unexpected error."));
}