#![allow(clippy::excessive_precision)]
use crate::Error;
use pyo3::{Python, prelude::*};
pub(crate) const EDGE_VALUES: &[f64] = &[
0.0,
-0.0,
f64::INFINITY,
f64::NEG_INFINITY,
f64::NAN,
-f64::NAN,
f64::from_bits(0x7FF8_0000_0000_0001_u64),
f64::from_bits(0x7FF0_0000_0000_0001_u64),
f64::MIN_POSITIVE * 0.5,
-f64::MIN_POSITIVE * 0.5,
5e-324,
-5e-324,
f64::MIN_POSITIVE,
f64::MIN_POSITIVE * 2.0,
f64::MAX,
f64::MIN,
f64::MAX * 0.5,
-f64::MAX * 0.5,
1e308,
-1e308,
710.0,
709.782712893384,
-745.0,
-745.1332191019411,
1e-10,
-1e-10,
1e-300,
-1e-300,
1e-308,
-1e-308,
1.0,
-1.0,
0.5,
-0.5,
2.0,
-2.0,
1.5,
-1.5,
3.0, -3.0,
1.0 - 1e-15,
1.0 + 1e-15,
f64::EPSILON,
1.0 - f64::EPSILON,
1.0 + f64::EPSILON,
1.0000000000000002, -1.0000000000000002,
0.9999999999999999, -0.9999999999999999,
-0.9999999999999999, -1.0 + 1e-15, -1.0000000000000002, -1.0,
-2.0,
-3.0,
-0.5, std::f64::consts::E,
std::f64::consts::LN_2,
std::f64::consts::LOG10_E,
std::f64::consts::PI,
-std::f64::consts::PI,
std::f64::consts::FRAC_PI_2,
-std::f64::consts::FRAC_PI_2,
std::f64::consts::FRAC_PI_4,
-std::f64::consts::FRAC_PI_4,
std::f64::consts::TAU,
1.5 * std::f64::consts::PI, 1e15,
-1e15,
0.49999999999999994,
0.50000000000000006,
-0.49999999999999994,
-0.50000000000000006,
];
pub(crate) fn unwrap<'py>(
py: Python<'py>,
py_v: PyResult<Bound<'py, PyAny>>,
v: Result<f64, crate::Error>,
) -> Option<(f64, f64)> {
match py_v {
Ok(py_v) => {
let py_v: f64 = py_v.extract().expect("failed to extract");
Some((py_v, v.unwrap()))
}
Err(e) => {
if e.is_instance_of::<pyo3::exceptions::PyValueError>(py) {
assert_eq!(v.err(), Some(Error::EDOM));
} else if e.is_instance_of::<pyo3::exceptions::PyOverflowError>(py) {
assert_eq!(v.err(), Some(Error::ERANGE));
} else {
panic!();
}
None
}
}
}
pub(crate) fn test_math_1(x: f64, func_name: &str, rs_func: impl Fn(f64) -> crate::Result<f64>) {
let rs_result = rs_func(x);
pyo3::Python::attach(|py| {
let math = pyo3::types::PyModule::import(py, "math").unwrap();
let py_func = math.getattr(func_name).unwrap();
let r = py_func.call1((x,));
let Some((py_result, rs_result)) = unwrap(py, r, rs_result) else {
return;
};
if py_result.is_nan() && rs_result.is_nan() {
return;
}
assert_eq!(
py_result.to_bits(),
rs_result.to_bits(),
"{func_name}({x}): py={py_result} vs rs={rs_result}"
);
});
}
pub(crate) fn with_py_math<F, R>(f: F) -> R
where
F: FnOnce(Python, &pyo3::Bound<pyo3::types::PyModule>) -> R,
{
pyo3::Python::attach(|py| {
let math = pyo3::types::PyModule::import(py, "math").unwrap();
f(py, &math)
})
}
pub(crate) fn assert_f64_eq(py: f64, rs: f64, context: impl std::fmt::Display) {
if py.is_nan() && rs.is_nan() {
return;
}
assert_eq!(
py.to_bits(),
rs.to_bits(),
"{}: py={} vs rs={}",
context,
py,
rs
);
}
pub(crate) fn test_math_2(
x: f64,
y: f64,
func_name: &str,
rs_func: impl Fn(f64, f64) -> crate::Result<f64>,
) {
let rs_result = rs_func(x, y);
pyo3::Python::attach(|py| {
let math = pyo3::types::PyModule::import(py, "math").unwrap();
let py_func = math.getattr(func_name).unwrap();
let r = py_func.call1((x, y));
match r {
Ok(py_val) => {
let py_f: f64 = py_val.extract().unwrap();
let rs_val = rs_result.unwrap_or_else(|e| {
panic!("{func_name}({x}, {y}): py={py_f} but rs returned error {e:?}")
});
if py_f.is_nan() && rs_val.is_nan() {
return;
}
assert_eq!(
py_f.to_bits(),
rs_val.to_bits(),
"{func_name}({x}, {y}): py={py_f} vs rs={rs_val}"
);
}
Err(e) => {
let rs_err = rs_result.as_ref().err();
if e.is_instance_of::<pyo3::exceptions::PyValueError>(py) {
assert_eq!(
rs_err,
Some(&Error::EDOM),
"{func_name}({x}, {y}): py raised ValueError but rs={rs_err:?}"
);
} else if e.is_instance_of::<pyo3::exceptions::PyOverflowError>(py) {
assert_eq!(
rs_err,
Some(&Error::ERANGE),
"{func_name}({x}, {y}): py raised OverflowError but rs={rs_err:?}"
);
} else {
panic!("{func_name}({x}, {y}): py raised unexpected error {e}");
}
}
}
});
}