From 8313a32b8152165dc38e91786635e41face232f8 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 13 Jan 2026 22:04:18 +0900 Subject: [PATCH 1/4] more edge values --- .gitignore | 1 + src/math/integer.rs | 33 +++++++++++++++++++---- src/test.rs | 64 ++++++++++++++++++++++++++++++++++++++------- 3 files changed, 83 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 96ef6c0..66113fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target Cargo.lock +cpython diff --git a/src/math/integer.rs b/src/math/integer.rs index 3638ac9..3c11080 100644 --- a/src/math/integer.rs +++ b/src/math/integer.rs @@ -715,7 +715,7 @@ mod tests { use pyo3::prelude::*; /// Edge i64 values for testing integer math functions (gcd, lcm, isqrt, factorial, comb, perm) - const EDGE_I64: [i64; 24] = [ + const EDGE_I64: [i64; 44] = [ // Zero and small values 0, 1, @@ -726,27 +726,50 @@ mod tests { 7, 13, 97, - // Powers of 2 + 127, // table boundary in comb/perm + 128, // Table boundary + 1 + // Powers of 2 and boundaries 64, + 63, // 2^6 - 1 + 65, // 2^6 + 1 1024, - 65536, + 65535, // 2^16 - 1 + 65536, // 2^16 + 65537, // 2^16 + 1 (Fermat prime) // Factorial-relevant + 12, // 12! = 479001600 fits in u32 + 13, // 13! overflows u32 20, // 20! fits in u64 21, // 21! overflows u64 + 170, // factorial(170) is the largest that fits in f64 + 171, // factorial(171) overflows f64 + // Comb/perm algorithm switching points + 34, // FAST_COMB_LIMITS1 boundary + 35, // Large values 1_000_000, -1_000_000, i32::MAX as i64, + i32::MAX as i64 + 1, i32::MIN as i64, + i32::MIN as i64 - 1, // Near i64 bounds i64::MAX, i64::MIN, i64::MAX - 1, i64::MIN + 1, // Square root boundaries - (1i64 << 31) - 1, // sqrt fits in u32 - 1i64 << 32, // sqrt boundary + (1i64 << 15) - 1, // 32767, sqrt = 181 + 1i64 << 16, // 65536, sqrt = 256 (exact) + (1i64 << 31) - 1, // sqrt fits in u16 + 1i64 << 32, // sqrt boundary (exact power of 2) + (1i64 << 32) - 1, // near sqrt boundary + (1i64 << 32) + 1, // just above sqrt boundary (1i64 << 62) - 1, // large but valid for isqrt + 1i64 << 62, // exact power of 2 + // Near perfect squares + 99, // sqrt(99) = 9.949... + 101, // sqrt(101) = 10.049... ]; fn test_gcd_impl(args: &[i64]) { diff --git a/src/test.rs b/src/test.rs index cf9ebf7..d33f0c6 100644 --- a/src/test.rs +++ b/src/test.rs @@ -3,7 +3,7 @@ use pyo3::{Python, prelude::*}; /// Edge values for testing floating-point functions. /// Includes: zeros, infinities, various NaNs, subnormals, and values at different scales. -pub(crate) const EDGE_VALUES: [f64; 30] = [ +pub(crate) const EDGE_VALUES: [f64; 64] = [ // Zeros 0.0, -0.0, @@ -15,35 +15,79 @@ pub(crate) const EDGE_VALUES: [f64; 30] = [ -f64::NAN, // Additional NaN with different payload (quiet NaN with payload 1) f64::from_bits(0x7FF8_0000_0000_0001_u64), + // Signaling NaN (sNaN) - may trigger FP exceptions on some platforms + f64::from_bits(0x7FF0_0000_0000_0001_u64), // Subnormal (denormalized) values - f64::MIN_POSITIVE * 0.5, // smallest subnormal + f64::MIN_POSITIVE * 0.5, -f64::MIN_POSITIVE * 0.5, + 5e-324, + -5e-324, // Boundary values - f64::MIN_POSITIVE, // smallest positive normal - f64::MAX, // largest finite - f64::MIN, // most negative finite (not smallest!) + f64::MIN_POSITIVE, + f64::MAX, + f64::MIN, // Near-infinity large values f64::MAX * 0.5, -f64::MAX * 0.5, 1e308, -1e308, + // Overflow/underflow thresholds for exp + 710.0, + -745.0, // Small scale 1e-10, -1e-10, 1e-300, + -1e-300, // Normal scale 1.0, -1.0, 0.5, -0.5, 2.0, - // Trigonometric special values (where sin/cos/tan have exact or near-zero results) - std::f64::consts::PI, // sin(PI) ≈ 0 + -2.0, + 3.0, // for cbrt + -3.0, + // Values near 1.0 (log, expm1, log1p, acosh boundary) + 1.0 - 1e-15, + 1.0 + 1e-15, + f64::EPSILON, + 1.0 - f64::EPSILON, + 1.0 + f64::EPSILON, + // asin/acos domain boundaries [-1, 1] + 1.0000000000000002, // just outside domain (1 + eps) + -1.0000000000000002, + // atanh domain boundaries (-1, 1) + 0.9999999999999999, // just inside domain + -0.9999999999999999, + // log1p domain boundary (> -1) + -0.9999999999999999, // just above -1 + -1.0 + 1e-15, // very close to -1 + // gamma/lgamma poles (negative integers) + -1.0, + -2.0, + -3.0, + -0.5, // gamma(-0.5) = -2*sqrt(pi) + // Mathematical constants + std::f64::consts::E, + std::f64::consts::LN_2, + std::f64::consts::LOG10_E, + // Trigonometric special values + std::f64::consts::PI, -std::f64::consts::PI, - std::f64::consts::FRAC_PI_2, // cos(PI/2) ≈ 0 + std::f64::consts::FRAC_PI_2, -std::f64::consts::FRAC_PI_2, - std::f64::consts::FRAC_PI_4, // tan(PI/4) = 1 - std::f64::consts::TAU, // sin(2*PI) ≈ 0, cos(2*PI) = 1 + std::f64::consts::FRAC_PI_4, + std::f64::consts::TAU, + 1.5 * std::f64::consts::PI, // 3π/2 + // Large values for trig (precision loss) + 1e15, + -1e15, + // Near-integer values (ceil, floor, trunc, round) + 0.49999999999999994, + 0.50000000000000006, + -0.49999999999999994, + -0.50000000000000006, ]; pub(crate) fn unwrap<'py>( From 0c83ff1012066d568b877a0887622a396d063218 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 15 Jan 2026 09:25:10 +0900 Subject: [PATCH 2/4] try --- src/cmath/exponential.rs | 5 +++-- src/cmath/misc.rs | 37 ++++++++++++++++++++++++++++++++----- src/math/integer.rs | 14 +++++++------- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/cmath/exponential.rs b/src/cmath/exponential.rs index f306065..9b3e793 100644 --- a/src/cmath/exponential.rs +++ b/src/cmath/exponential.rs @@ -4,7 +4,7 @@ use super::{ CM_LARGE_DOUBLE, CM_LOG_LARGE_DOUBLE, INF, M_LN2, N, P, P12, P14, P34, U, c, special_type, special_value, }; -use crate::{Error, Result, m}; +use crate::{Error, Result, m, mul_add}; use num_complex::Complex64; // Local constants @@ -160,7 +160,8 @@ pub(crate) fn ln(z: Complex64) -> Result { if (0.71..=1.73).contains(&h) { let am = if ax > ay { ax } else { ay }; // max(ax, ay) let an = if ax > ay { ay } else { ax }; // min(ax, ay) - m::log1p((am - 1.0) * (am + 1.0) + an * an) / 2.0 + let log1p_arg = mul_add(am - 1.0, am + 1.0, an * an); + m::log1p(log1p_arg) / 2.0 } else { m::log(h) } diff --git a/src/cmath/misc.rs b/src/cmath/misc.rs index 69514e2..f7136fc 100644 --- a/src/cmath/misc.rs +++ b/src/cmath/misc.rs @@ -28,14 +28,41 @@ pub fn phase(z: Complex64) -> Result { } } +#[inline] +fn c_abs_raw(z: Complex64) -> f64 { + if !z.re.is_finite() || !z.im.is_finite() { + // C99 rules: if either part is infinite, return infinity, + // even if the other part is NaN. + if z.re.is_infinite() { + return m::fabs(z.re); + } + if z.im.is_infinite() { + return m::fabs(z.im); + } + return f64::NAN; + } + m::hypot(z.re, z.im) +} + +#[inline] +fn c_abs_checked(z: Complex64) -> Result { + if !z.re.is_finite() || !z.im.is_finite() { + return Ok(c_abs_raw(z)); + } + crate::err::set_errno(0); + let r = m::hypot(z.re, z.im); + if r.is_infinite() { + Err(Error::ERANGE) + } else { + Ok(r) + } +} + /// Convert z to polar coordinates (r, phi). #[inline] pub fn polar(z: Complex64) -> Result<(f64, f64)> { let phi = m::atan2(z.im, z.re); - let r = m::hypot(z.re, z.im); - if r.is_infinite() && z.re.is_finite() && z.im.is_finite() { - return Err(Error::ERANGE); - } + let r = c_abs_checked(z)?; Ok((r, phi)) } @@ -94,7 +121,7 @@ pub fn isinf(z: Complex64) -> bool { /// Complex absolute value (magnitude). #[inline] pub fn abs(z: Complex64) -> f64 { - m::hypot(z.re, z.im) + c_abs_raw(z) } /// Determine whether two complex numbers are close in value. diff --git a/src/math/integer.rs b/src/math/integer.rs index 3c11080..2ef9331 100644 --- a/src/math/integer.rs +++ b/src/math/integer.rs @@ -715,7 +715,7 @@ mod tests { use pyo3::prelude::*; /// Edge i64 values for testing integer math functions (gcd, lcm, isqrt, factorial, comb, perm) - const EDGE_I64: [i64; 44] = [ + const EDGE_I64: &[i64] = &[ // Zero and small values 0, 1, @@ -982,9 +982,9 @@ mod tests { #[test] fn edgetest_gcd() { // Test all edge values - gcd handles arbitrary large integers - for &a in &EDGE_I64 { + for &a in EDGE_I64 { test_gcd_impl(&[a]); - for &b in &EDGE_I64 { + for &b in EDGE_I64 { test_gcd_impl(&[a, b]); } } @@ -997,9 +997,9 @@ mod tests { #[test] fn edgetest_lcm() { // Test all edge values - lcm handles arbitrary large integers - for &a in &EDGE_I64 { + for &a in EDGE_I64 { test_lcm_impl(&[a]); - for &b in &EDGE_I64 { + for &b in EDGE_I64 { test_lcm_impl(&[a, b]); } } @@ -1011,7 +1011,7 @@ mod tests { #[test] fn edgetest_isqrt() { - for &n in &EDGE_I64 { + for &n in EDGE_I64 { test_isqrt_impl(n); } // Additional boundary cases @@ -1031,7 +1031,7 @@ mod tests { #[test] fn edgetest_factorial() { - for &n in &EDGE_I64 { + for &n in EDGE_I64 { // factorial only makes sense for reasonable n values if n >= -10 && n <= 170 { test_factorial_impl(n); From ea2bc5deeacf45df5cbea5e9b1545038428d8e95 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 15 Jan 2026 09:33:01 +0900 Subject: [PATCH 3/4] more values --- src/cmath/exponential.rs | 20 ++++++++-------- src/cmath/misc.rs | 12 +++++----- src/cmath/trigonometric.rs | 48 +++++++++++++++++++------------------- src/math.rs | 8 +++---- src/math/exponential.rs | 22 ++++++++--------- src/math/integer.rs | 24 +++++++++---------- src/math/misc.rs | 28 +++++++++++----------- src/math/trigonometric.rs | 28 +++++++++++----------- src/test.rs | 12 +++++++++- 9 files changed, 106 insertions(+), 96 deletions(-) diff --git a/src/cmath/exponential.rs b/src/cmath/exponential.rs index 9b3e793..d11bf7b 100644 --- a/src/cmath/exponential.rs +++ b/src/cmath/exponential.rs @@ -329,8 +329,8 @@ mod tests { #[test] fn edgetest_sqrt() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_sqrt(re, im); } } @@ -338,8 +338,8 @@ mod tests { #[test] fn edgetest_exp() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_exp(re, im); } } @@ -347,8 +347,8 @@ mod tests { #[test] fn edgetest_log_n() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_log_n(re, im); } } @@ -356,8 +356,8 @@ mod tests { #[test] fn edgetest_log10() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_log10(re, im); } } @@ -374,8 +374,8 @@ mod tests { } } // Additional edge cases with imaginary parts - for &z_re in &EDGE_VALUES { - for &z_im in &EDGE_VALUES { + for &z_re in EDGE_VALUES { + for &z_im in EDGE_VALUES { test_log(z_re, z_im, 2.0, 0.0); test_log(z_re, z_im, 0.5, 0.0); } diff --git a/src/cmath/misc.rs b/src/cmath/misc.rs index f7136fc..0e3ef3a 100644 --- a/src/cmath/misc.rs +++ b/src/cmath/misc.rs @@ -212,8 +212,8 @@ mod tests { #[test] fn edgetest_phase() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_phase_impl(re, im); } } @@ -277,8 +277,8 @@ mod tests { #[test] fn edgetest_polar() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_polar_impl(re, im); } } @@ -324,8 +324,8 @@ mod tests { #[test] fn edgetest_rect() { - for &r in &EDGE_VALUES { - for &phi in &EDGE_VALUES { + for &r in EDGE_VALUES { + for &phi in EDGE_VALUES { test_rect_impl(r, phi); } } diff --git a/src/cmath/trigonometric.rs b/src/cmath/trigonometric.rs index 2ffb128..6ea42eb 100644 --- a/src/cmath/trigonometric.rs +++ b/src/cmath/trigonometric.rs @@ -421,8 +421,8 @@ mod tests { #[test] fn edgetest_sin() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_sin(re, im); } } @@ -430,8 +430,8 @@ mod tests { #[test] fn edgetest_cos() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_cos(re, im); } } @@ -439,8 +439,8 @@ mod tests { #[test] fn edgetest_tan() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_tan(re, im); } } @@ -448,8 +448,8 @@ mod tests { #[test] fn edgetest_sinh() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_sinh(re, im); } } @@ -457,8 +457,8 @@ mod tests { #[test] fn edgetest_cosh() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_cosh(re, im); } } @@ -466,8 +466,8 @@ mod tests { #[test] fn edgetest_tanh() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_tanh(re, im); } } @@ -475,8 +475,8 @@ mod tests { #[test] fn edgetest_asin() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_asin(re, im); } } @@ -484,8 +484,8 @@ mod tests { #[test] fn edgetest_acos() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_acos(re, im); } } @@ -493,8 +493,8 @@ mod tests { #[test] fn edgetest_atan() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_atan(re, im); } } @@ -502,8 +502,8 @@ mod tests { #[test] fn edgetest_asinh() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_asinh(re, im); } } @@ -511,8 +511,8 @@ mod tests { #[test] fn edgetest_acosh() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_acosh(re, im); } } @@ -520,8 +520,8 @@ mod tests { #[test] fn edgetest_atanh() { - for &re in &EDGE_VALUES { - for &im in &EDGE_VALUES { + for &re in EDGE_VALUES { + for &im in EDGE_VALUES { test_atanh(re, im); } } diff --git a/src/math.rs b/src/math.rs index 2ea1e48..49ed90d 100644 --- a/src/math.rs +++ b/src/math.rs @@ -206,14 +206,14 @@ mod tests { #[test] fn edgetest_degrees() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_degrees(x); } } #[test] fn edgetest_radians() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_radians(x); } } @@ -262,8 +262,8 @@ mod tests { #[test] fn edgetest_hypot() { - for &x in &crate::test::EDGE_VALUES { - for &y in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { + for &y in crate::test::EDGE_VALUES { test_hypot(&[x, y]); } } diff --git a/src/math/exponential.rs b/src/math/exponential.rs index be2c47c..1b38008 100644 --- a/src/math/exponential.rs +++ b/src/math/exponential.rs @@ -334,71 +334,71 @@ mod tests { #[test] fn edgetest_exp() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_exp(x); } } #[test] fn edgetest_exp2() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_exp2(x); } } #[test] fn edgetest_expm1() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_expm1(x); } } #[test] fn edgetest_sqrt() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_sqrt(x); } } #[test] fn edgetest_cbrt() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_cbrt(x); } } #[test] fn edgetest_log_n() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_log_n(x); } } #[test] fn edgetest_log10() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_log10(x); } } #[test] fn edgetest_log2() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_log2(x); } } #[test] fn edgetest_log1p() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_log1p(x); } } #[test] fn edgetest_pow() { - for &x in &crate::test::EDGE_VALUES { - for &y in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { + for &y in crate::test::EDGE_VALUES { test_pow(x, y); } } diff --git a/src/math/integer.rs b/src/math/integer.rs index 2ef9331..02e26b1 100644 --- a/src/math/integer.rs +++ b/src/math/integer.rs @@ -726,25 +726,25 @@ mod tests { 7, 13, 97, - 127, // table boundary in comb/perm - 128, // Table boundary + 1 + 127, // table boundary in comb/perm + 128, // Table boundary + 1 // Powers of 2 and boundaries 64, 63, // 2^6 - 1 65, // 2^6 + 1 1024, - 65535, // 2^16 - 1 - 65536, // 2^16 - 65537, // 2^16 + 1 (Fermat prime) + 65535, // 2^16 - 1 + 65536, // 2^16 + 65537, // 2^16 + 1 (Fermat prime) // Factorial-relevant - 12, // 12! = 479001600 fits in u32 - 13, // 13! overflows u32 - 20, // 20! fits in u64 - 21, // 21! overflows u64 + 12, // 12! = 479001600 fits in u32 + 13, // 13! overflows u32 + 20, // 20! fits in u64 + 21, // 21! overflows u64 170, // factorial(170) is the largest that fits in f64 171, // factorial(171) overflows f64 // Comb/perm algorithm switching points - 34, // FAST_COMB_LIMITS1 boundary + 34, // FAST_COMB_LIMITS1 boundary 35, // Large values 1_000_000, @@ -768,8 +768,8 @@ mod tests { (1i64 << 62) - 1, // large but valid for isqrt 1i64 << 62, // exact power of 2 // Near perfect squares - 99, // sqrt(99) = 9.949... - 101, // sqrt(101) = 10.049... + 99, // sqrt(99) = 9.949... + 101, // sqrt(101) = 10.049... ]; fn test_gcd_impl(args: &[i64]) { diff --git a/src/math/misc.rs b/src/math/misc.rs index 42d9e5a..4b96823 100644 --- a/src/math/misc.rs +++ b/src/math/misc.rs @@ -207,7 +207,7 @@ mod tests { use super::*; /// Edge integer values for testing functions like ldexp - const EDGE_INTS: [i32; 9] = [0, 1, -1, 100, -100, 1024, -1024, i32::MAX, i32::MIN]; + const EDGE_INTS: &[i32] = &[0, 1, -1, 100, -100, 1024, -1024, i32::MAX, i32::MIN]; fn test_ldexp(x: f64, i: i32) { use pyo3::prelude::*; @@ -310,15 +310,15 @@ mod tests { #[test] fn edgetest_frexp() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_frexp(x); } } #[test] fn edgetest_ldexp() { - for &x in &crate::test::EDGE_VALUES { - for &i in &EDGE_INTS { + for &x in crate::test::EDGE_VALUES { + for &i in EDGE_INTS { test_ldexp(x, i); } } @@ -326,15 +326,15 @@ mod tests { #[test] fn edgetest_modf() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_modf(x); } } #[test] fn edgetest_fmod() { - for &x in &crate::test::EDGE_VALUES { - for &y in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { + for &y in crate::test::EDGE_VALUES { test_fmod(x, y); } } @@ -342,8 +342,8 @@ mod tests { #[test] fn edgetest_remainder() { - for &x in &crate::test::EDGE_VALUES { - for &y in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { + for &y in crate::test::EDGE_VALUES { test_remainder(x, y); } } @@ -351,8 +351,8 @@ mod tests { #[test] fn edgetest_copysign() { - for &x in &crate::test::EDGE_VALUES { - for &y in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { + for &y in crate::test::EDGE_VALUES { test_copysign(x, y); } } @@ -360,7 +360,7 @@ mod tests { #[test] fn edgetest_ulp() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_ulp(x); } } @@ -573,8 +573,8 @@ mod tests { #[test] fn edgetest_fma() { - for &x in &crate::test::EDGE_VALUES { - for &y in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { + for &y in crate::test::EDGE_VALUES { test_fma_impl(x, y, 0.0); test_fma_impl(x, y, 1.0); } diff --git a/src/math/trigonometric.rs b/src/math/trigonometric.rs index 285b767..150729f 100644 --- a/src/math/trigonometric.rs +++ b/src/math/trigonometric.rs @@ -135,50 +135,50 @@ mod tests { // Trigonometric edge tests #[test] fn edgetest_sin() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_sin(x); } } #[test] fn edgetest_cos() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_cos(x); } } #[test] fn edgetest_tan() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_tan(x); } } #[test] fn edgetest_asin() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_asin(x); } } #[test] fn edgetest_acos() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_acos(x); } } #[test] fn edgetest_atan() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_atan(x); } } #[test] fn edgetest_atan2() { - for &y in &crate::test::EDGE_VALUES { - for &x in &crate::test::EDGE_VALUES { + for &y in crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_atan2(y, x); } } @@ -187,42 +187,42 @@ mod tests { // Hyperbolic edge tests #[test] fn edgetest_tanh() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_tanh(x); } } #[test] fn edgetest_asinh() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_asinh(x); } } #[test] fn edgetest_sinh() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_sinh(x); } } #[test] fn edgetest_cosh() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_cosh(x); } } #[test] fn edgetest_acosh() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_acosh(x); } } #[test] fn edgetest_atanh() { - for &x in &crate::test::EDGE_VALUES { + for &x in crate::test::EDGE_VALUES { test_atanh(x); } } diff --git a/src/test.rs b/src/test.rs index d33f0c6..62831ea 100644 --- a/src/test.rs +++ b/src/test.rs @@ -3,7 +3,7 @@ use pyo3::{Python, prelude::*}; /// Edge values for testing floating-point functions. /// Includes: zeros, infinities, various NaNs, subnormals, and values at different scales. -pub(crate) const EDGE_VALUES: [f64; 64] = [ +pub(crate) const EDGE_VALUES: &[f64] = &[ // Zeros 0.0, -0.0, @@ -24,6 +24,7 @@ pub(crate) const EDGE_VALUES: [f64; 64] = [ -5e-324, // Boundary values f64::MIN_POSITIVE, + f64::MIN_POSITIVE * 2.0, f64::MAX, f64::MIN, // Near-infinity large values @@ -33,12 +34,16 @@ pub(crate) const EDGE_VALUES: [f64; 64] = [ -1e308, // Overflow/underflow thresholds for exp 710.0, + 709.782712893384, -745.0, + -745.1332191019411, // Small scale 1e-10, -1e-10, 1e-300, -1e-300, + 1e-308, + -1e-308, // Normal scale 1.0, -1.0, @@ -46,6 +51,8 @@ pub(crate) const EDGE_VALUES: [f64; 64] = [ -0.5, 2.0, -2.0, + 1.5, + -1.5, 3.0, // for cbrt -3.0, // Values near 1.0 (log, expm1, log1p, acosh boundary) @@ -63,6 +70,7 @@ pub(crate) const EDGE_VALUES: [f64; 64] = [ // log1p domain boundary (> -1) -0.9999999999999999, // just above -1 -1.0 + 1e-15, // very close to -1 + -1.0000000000000002, // just below -1 // gamma/lgamma poles (negative integers) -1.0, -2.0, @@ -78,6 +86,7 @@ pub(crate) const EDGE_VALUES: [f64; 64] = [ 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, // 3π/2 // Large values for trig (precision loss) @@ -90,6 +99,7 @@ pub(crate) const EDGE_VALUES: [f64; 64] = [ -0.50000000000000006, ]; + pub(crate) fn unwrap<'py>( py: Python<'py>, py_v: PyResult>, From 31f1781b8a5e5700bbc92793c4a61bd4c43ff922 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 16 Jan 2026 21:11:27 +0900 Subject: [PATCH 4/4] Fix builds --- src/cmath.rs | 3 +-- src/cmath/misc.rs | 3 +-- src/math.rs | 30 ++++++++++++++++-------------- src/math/integer.rs | 6 +++--- src/test.rs | 5 ++++- 5 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/cmath.rs b/src/cmath.rs index c66242f..1878d98 100644 --- a/src/cmath.rs +++ b/src/cmath.rs @@ -178,8 +178,7 @@ pub(crate) mod tests { } Err(e) => { // CPython raised an exception - check we got an error too - if rs_result.is_ok() { - let rs = rs_result.unwrap(); + if let Ok(rs) = rs_result { // Some special cases may return values for domain errors in Python // Check if it's a domain error if e.is_instance_of::(py) { diff --git a/src/cmath/misc.rs b/src/cmath/misc.rs index 0e3ef3a..7510486 100644 --- a/src/cmath/misc.rs +++ b/src/cmath/misc.rs @@ -196,8 +196,7 @@ mod tests { } Err(e) => { // Python raised an exception - check we got an error too - if rs_result.is_ok() { - let rs_val = rs_result.unwrap(); + if let Ok(rs_val) = rs_result { if e.is_instance_of::(py) { panic!("phase({re}, {im}): py raised ValueError but rs={rs_val}"); } else if e.is_instance_of::(py) { diff --git a/src/math.rs b/src/math.rs index 49ed90d..d9bc17f 100644 --- a/src/math.rs +++ b/src/math.rs @@ -114,17 +114,19 @@ pub fn hypot(coords: &[f64]) -> f64 { let mut max = 0.0_f64; let mut found_nan = false; - let abs_coords: Vec = coords - .iter() - .map(|&x| { - let ax = x.abs(); - found_nan |= ax.is_nan(); - if ax > max { - max = ax; - } - ax - }) - .collect(); + let mut abs_coords = Vec::with_capacity(n); + + for &x in coords { + let ax = x.abs(); + // Use is_nan() check separately to prevent compiler from + // reordering NaN detection relative to max comparison + if ax.is_nan() { + found_nan = true; + } else if ax > max { + max = ax; + } + abs_coords.push(ax); + } aggregate::vector_norm(&abs_coords, max, found_nan) } @@ -221,9 +223,9 @@ mod tests { // Constants test #[test] fn test_constants() { - assert!((PI - 3.141592653589793).abs() < 1e-15); - assert!((E - 2.718281828459045).abs() < 1e-15); - assert!((TAU - 6.283185307179586).abs() < 1e-15); + assert_eq!(PI, std::f64::consts::PI); + assert_eq!(E, std::f64::consts::E); + assert_eq!(TAU, std::f64::consts::TAU); assert!(INF.is_infinite() && INF > 0.0); assert!(NAN.is_nan()); } diff --git a/src/math/integer.rs b/src/math/integer.rs index 02e26b1..87537b0 100644 --- a/src/math/integer.rs +++ b/src/math/integer.rs @@ -1033,7 +1033,7 @@ mod tests { fn edgetest_factorial() { for &n in EDGE_I64 { // factorial only makes sense for reasonable n values - if n >= -10 && n <= 170 { + if (-10..=170).contains(&n) { test_factorial_impl(n); } } @@ -1051,7 +1051,7 @@ mod tests { let vals: Vec = EDGE_I64 .iter() .copied() - .filter(|&x| x >= -10 && x <= 200) + .filter(|&x| (-10..=200).contains(&x)) .collect(); for &n in &vals { for &k in &vals { @@ -1070,7 +1070,7 @@ mod tests { let vals: Vec = EDGE_I64 .iter() .copied() - .filter(|&x| x >= -10 && x <= 100) + .filter(|&x| (-10..=100).contains(&x)) .collect(); for &n in &vals { for &k in &vals { diff --git a/src/test.rs b/src/test.rs index 62831ea..8fa3c5b 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,3 +1,6 @@ +// Allow excessive precision in edge values - these are intentional test cases +#![allow(clippy::excessive_precision)] + use crate::Error; use pyo3::{Python, prelude::*}; @@ -107,7 +110,7 @@ pub(crate) fn unwrap<'py>( ) -> Option<(f64, f64)> { match py_v { Ok(py_v) => { - let py_v: f64 = py_v.extract().ok().expect("failed to extract"); + let py_v: f64 = py_v.extract().expect("failed to extract"); Some((py_v, v.unwrap())) } Err(e) => {