diff --git a/src/tests/simd_math_targeted_edges/binary_misc.rs b/src/tests/simd_math_targeted_edges/binary_misc.rs index da80944..0456362 100644 --- a/src/tests/simd_math_targeted_edges/binary_misc.rs +++ b/src/tests/simd_math_targeted_edges/binary_misc.rs @@ -128,6 +128,125 @@ fn run_f32_log10_domain_and_mixed_lanes() { } } +fn run_f32_binary_misc_adversarial_lanes() { + let log10_inputs = [ + f32::from_bits(1), + f32::from_bits(2), + f32::MIN_POSITIVE, + 9.999_999e-11, + 1.0e-10, + 10.0, + 1000.0, + 0.0, + -0.0, + -1.0, + f32::INFINITY, + f32::NAN, + ]; + for chunk in log10_inputs.chunks(S::Vf32::WIDTH) { + let xv = S::Vf32::load_from_slice(chunk); + let out = xv.log10_u35(); + for lane in 0..chunk.len() { + let x = chunk[lane]; + assert_f32_contract( + "log10_u35", + x, + out[lane], + x.log10(), + contracts::LOG10_U35_F32_MAX_ULP, + ) + .unwrap_or_else(|e| panic!("adversarial log10_u35({x:?}) {e}")); + } + } + + let atan2_cases = [ + (0.0f32, -0.0f32), + (-0.0, -0.0), + (0.0, 0.0), + (-0.0, 0.0), + (f32::from_bits(1), -f32::from_bits(1)), + (-f32::from_bits(1), -f32::from_bits(1)), + (1.0e-30, -1.0), + (-1.0e-30, -1.0), + (1.0, -1.0e-30), + (-1.0, -1.0e-30), + (f32::INFINITY, -f32::INFINITY), + (f32::NEG_INFINITY, -f32::INFINITY), + (f32::NAN, 1.0), + (1.0, f32::NAN), + ]; + for chunk in atan2_cases.chunks(S::Vf32::WIDTH) { + let mut ys = vec![0.0f32; chunk.len()]; + let mut xs = vec![0.0f32; chunk.len()]; + for (idx, (y, x)) in chunk.iter().copied().enumerate() { + ys[idx] = y; + xs[idx] = x; + } + + let yv = S::Vf32::load_from_slice(&ys); + let xv = S::Vf32::load_from_slice(&xs); + let out = yv.atan2_u35(xv); + for lane in 0..chunk.len() { + let y = ys[lane]; + let x = xs[lane]; + assert_f32_contract( + "atan2_u35", + y, + out[lane], + y.atan2(x), + contracts::ATAN2_U35_F32_MAX_ULP, + ) + .unwrap_or_else(|e| panic!("adversarial atan2_u35({y:?}, {x:?}) {e}")); + } + } + + let binary_cases = [ + (f32::MAX, f32::MIN_POSITIVE), + (f32::MIN_POSITIVE, f32::MAX), + (1.0e38, 1.0e-38), + (1.0e-38, 1.0e38), + (-1.0e38, 1.0e-38), + (6.0, 3.0), + (6.0, -3.0), + (-6.0, 3.0), + (-6.0, -3.0), + (0.0, 3.0), + (-0.0, 3.0), + (f32::INFINITY, 2.0), + (2.0, 0.0), + (f32::NAN, 2.0), + (2.0, f32::NAN), + ]; + + for chunk in binary_cases.chunks(S::Vf32::WIDTH) { + let mut xs = vec![0.0f32; chunk.len()]; + let mut ys = vec![0.0f32; chunk.len()]; + for (idx, (x, y)) in chunk.iter().copied().enumerate() { + xs[idx] = x; + ys[idx] = y; + } + + let xv = S::Vf32::load_from_slice(&xs); + let yv = S::Vf32::load_from_slice(&ys); + let h = xv.hypot_u35(yv); + let r = xv.fmod(yv); + for lane in 0..chunk.len() { + let x = xs[lane]; + let y = ys[lane]; + assert_f32_contract( + "hypot_u35", + x, + h[lane], + x.hypot(y), + contracts::HYPOT_U35_F32_MAX_ULP, + ) + .unwrap_or_else(|e| panic!("adversarial hypot_u35({x:?}, {y:?}) {e}")); + assert_f32_contract("fmod", x, r[lane], x % y, 0) + .unwrap_or_else(|e| panic!("adversarial fmod({x:?}, {y:?}) {e}")); + } + } +} + simd_math_targeted_all_backends!( f32_atan2_signed_zero_and_quadrants, run_f32_atan2_signed_zero_and_quadrants @@ -137,6 +256,10 @@ simd_math_targeted_all_backends!( f32_log10_domain_and_mixed_lanes, run_f32_log10_domain_and_mixed_lanes ); +simd_math_targeted_all_backends!( + f32_binary_misc_adversarial_lanes, + run_f32_binary_misc_adversarial_lanes +); fn run_f64_atan2_signed_zero_and_quadrants() { let ys = [ @@ -278,7 +401,6 @@ simd_math_targeted_all_backends!( ); fn run_f64_binary_misc_adversarial_lanes() { - // log10_u35: adversarial around denormals and exact powers of ten. let log10_inputs = [ f64::MIN_POSITIVE, f64::from_bits(1), @@ -310,7 +432,6 @@ fn run_f64_binary_misc_adversarial_lanes() { } } - // atan2_u35: signed-zero and near-axis quadrant behavior. let atan2_cases = [ (0.0, -0.0), (-0.0, -0.0), @@ -352,7 +473,6 @@ fn run_f64_binary_misc_adversarial_lanes() { } } - // hypot_u35 + fmod: scale extremes and quotient-threshold stress. let binary_cases = [ (f64::MAX, f64::MIN_POSITIVE), (f64::MIN_POSITIVE, f64::MAX), @@ -401,7 +521,70 @@ fn run_f64_binary_misc_adversarial_lanes() { } } +fn run_f64_fmod_zero_and_guard_boundaries() { + let exact_zero_cases = [ + (6.0f64, 3.0f64), + (6.0, -3.0), + (-6.0, 3.0), + (-6.0, -3.0), + (0.0, 3.0), + (-0.0, 3.0), + ]; + for chunk in exact_zero_cases.chunks(S::Vf64::WIDTH) { + let mut xs = vec![0.0f64; chunk.len()]; + let mut ys = vec![0.0f64; chunk.len()]; + for (idx, (x, y)) in chunk.iter().copied().enumerate() { + xs[idx] = x; + ys[idx] = y; + } + + let xv = S::Vf64::load_from_slice(&xs); + let yv = S::Vf64::load_from_slice(&ys); + let out = xv.fmod(yv); + for lane in 0..chunk.len() { + let x = xs[lane]; + let y = ys[lane]; + assert_f64_contract("fmod", x, out[lane], x % y, 0) + .unwrap_or_else(|e| panic!("zero-sign fmod({x:?}, {y:?}) {e}")); + } + } + + let q = 4_503_599_627_370_496.0f64; + let guard_cases = [ + ((q - 1.0) * 2.0, 2.0), + (q * 2.0, 2.0), + ((q + 1.0) * 2.0, 2.0), + (-((q - 1.0) * 2.0), 2.0), + (-(q * 2.0), 2.0), + (-((q + 1.0) * 2.0), 2.0), + ((q - 1.0) * -2.0, -2.0), + ((q + 1.0) * -2.0, -2.0), + ]; + for chunk in guard_cases.chunks(S::Vf64::WIDTH) { + let mut xs = vec![0.0f64; chunk.len()]; + let mut ys = vec![0.0f64; chunk.len()]; + for (idx, (x, y)) in chunk.iter().copied().enumerate() { + xs[idx] = x; + ys[idx] = y; + } + + let xv = S::Vf64::load_from_slice(&xs); + let yv = S::Vf64::load_from_slice(&ys); + let out = xv.fmod(yv); + for lane in 0..chunk.len() { + let x = xs[lane]; + let y = ys[lane]; + assert_f64_contract("fmod", x, out[lane], x % y, 0) + .unwrap_or_else(|e| panic!("guard-boundary fmod({x:?}, {y:?}) {e}")); + } + } +} + simd_math_targeted_all_backends!( f64_binary_misc_adversarial_lanes, run_f64_binary_misc_adversarial_lanes ); +simd_math_targeted_all_backends!( + f64_fmod_zero_and_guard_boundaries, + run_f64_fmod_zero_and_guard_boundaries +); diff --git a/src/tests/simd_math_targeted_edges/hyperbolic.rs b/src/tests/simd_math_targeted_edges/hyperbolic.rs index c8ad2d5..d0e6d9b 100644 --- a/src/tests/simd_math_targeted_edges/hyperbolic.rs +++ b/src/tests/simd_math_targeted_edges/hyperbolic.rs @@ -196,6 +196,31 @@ simd_math_targeted_all_backends!( run_f32_hyperbolic_special_values_and_mixed_lanes ); +fn run_f32_hyperbolic_signed_zero_semantics() { + let mut lanes = vec![0.0f32; S::Vf32::WIDTH]; + lanes[0] = -0.0; + + let input = S::Vf32::load_from_slice(&lanes); + let sinh = input.sinh_u35(); + let tanh = input.tanh_u35(); + + assert_eq!(sinh[0].to_bits(), (-0.0f32).sinh().to_bits()); + assert_eq!(tanh[0].to_bits(), (-0.0f32).tanh().to_bits()); + + if S::Vf32::WIDTH > 1 { + assert_eq!(sinh[1].to_bits(), 0.0f32.sinh().to_bits()); + assert_eq!(tanh[1].to_bits(), 0.0f32.tanh().to_bits()); + } + + let cosh = input.cosh_u35(); + assert_eq!(cosh[0].to_bits(), (-0.0f32).cosh().to_bits()); +} + +simd_math_targeted_all_backends!( + f32_hyperbolic_signed_zero_semantics, + run_f32_hyperbolic_signed_zero_semantics +); + fn push_boundary_triplet_f64(inputs: &mut Vec, center: f64) { inputs.push(f64::from_bits(center.to_bits().saturating_sub(1))); inputs.push(center); @@ -292,6 +317,46 @@ fn run_f64_hyperbolic_special_values_and_mixed_lanes() { } } +fn run_f64_hyperbolic_edges() { + let inputs = [ + -100.0f64, -40.0, -20.0, -10.0, -1.0, -0.5, -0.0, 0.0, 0.5, 1.0, 10.0, 20.0, 40.0, 100.0, + ]; + for chunk in inputs.chunks(S::Vf64::WIDTH) { + let v = S::Vf64::load_from_slice(chunk); + let sinh = v.sinh_u35(); + let cosh = v.cosh_u35(); + let tanh = v.tanh_u35(); + + for (lane, &x) in chunk.iter().enumerate() { + assert_f64_contract( + "sinh_u35", + x, + sinh[lane], + x.sinh(), + contracts::SINH_U35_F64_MAX_ULP, + ) + .unwrap_or_else(|e| panic!("{e}")); + assert_f64_contract( + "cosh_u35", + x, + cosh[lane], + x.cosh(), + contracts::COSH_U35_F64_MAX_ULP, + ) + .unwrap_or_else(|e| panic!("{e}")); + assert_f64_contract( + "tanh_u35", + x, + tanh[lane], + x.tanh(), + contracts::TANH_U35_F64_MAX_ULP, + ) + .unwrap_or_else(|e| panic!("{e}")); + } + } +} + +simd_math_targeted_all_backends!(f64_hyperbolic_edges, run_f64_hyperbolic_edges); simd_math_targeted_all_backends!( f64_hyperbolic_fast_path_boundaries, run_f64_hyperbolic_fast_path_boundaries @@ -301,6 +366,48 @@ simd_math_targeted_all_backends!( run_f64_hyperbolic_special_values_and_mixed_lanes ); +fn run_f64_hyperbolic_scalar_patch_cutover() { + let mut inputs = Vec::new(); + for center in [-1.0f64, 1.0] { + push_boundary_triplet_f64(&mut inputs, center); + } + inputs.extend_from_slice(&[-20.0, -1.0e-12, -0.0, 0.0, 1.0e-12, 20.0]); + + while !inputs.len().is_multiple_of(S::Vf64::WIDTH) { + inputs.push(0.25); + } + + for chunk in inputs.chunks(S::Vf64::WIDTH) { + let v = S::Vf64::load_from_slice(chunk); + let sinh = v.sinh_u35(); + let tanh = v.tanh_u35(); + + for (lane, &x) in chunk.iter().enumerate() { + assert_f64_contract( + "sinh_u35", + x, + sinh[lane], + x.sinh(), + contracts::SINH_U35_F64_MAX_ULP, + ) + .unwrap_or_else(|e| panic!("scalar-patch sinh_u35({x:?}) {e}")); + assert_f64_contract( + "tanh_u35", + x, + tanh[lane], + x.tanh(), + contracts::TANH_U35_F64_MAX_ULP, + ) + .unwrap_or_else(|e| panic!("scalar-patch tanh_u35({x:?}) {e}")); + } + } +} + +simd_math_targeted_all_backends!( + f64_hyperbolic_scalar_patch_cutover, + run_f64_hyperbolic_scalar_patch_cutover +); + fn run_f64_hyperbolic_signed_zero_semantics() { let mut lanes = vec![0.0f64; S::Vf64::WIDTH]; lanes[0] = -0.0; diff --git a/src/tests/simd_math_targeted_edges/inverse_hyperbolic.rs b/src/tests/simd_math_targeted_edges/inverse_hyperbolic.rs index cc382e7..750d15c 100644 --- a/src/tests/simd_math_targeted_edges/inverse_hyperbolic.rs +++ b/src/tests/simd_math_targeted_edges/inverse_hyperbolic.rs @@ -1,6 +1,18 @@ use super::*; use crate::math::{SimdMathF32InverseHyperbolic, SimdMathF64InverseHyperbolic}; +fn push_boundary_triplet_f32(inputs: &mut Vec, center: f32) { + inputs.push(f32::from_bits(center.to_bits().saturating_sub(1))); + inputs.push(center); + inputs.push(f32::from_bits(center.to_bits().saturating_add(1))); +} + +fn push_boundary_triplet_f64(inputs: &mut Vec, center: f64) { + inputs.push(f64::from_bits(center.to_bits().saturating_sub(1))); + inputs.push(center); + inputs.push(f64::from_bits(center.to_bits().saturating_add(1))); +} + fn run_f32_inverse_hyperbolic_domain_edges() { let acosh_inputs = [ 0.0f32, @@ -136,6 +148,108 @@ simd_math_targeted_all_backends!( run_f32_inverse_hyperbolic_mixed_lanes ); +fn run_f32_inverse_hyperbolic_threshold_boundaries() { + let mut asinh_inputs = Vec::new(); + let mut atanh_inputs = Vec::new(); + + for center in [-1.0e19f32, -0.05, -0.0, 0.0, 0.05, 1.0e19] { + push_boundary_triplet_f32(&mut asinh_inputs, center); + } + for center in [-0.75f32, -0.05, -0.0, 0.0, 0.05, 0.75] { + push_boundary_triplet_f32(&mut atanh_inputs, center); + } + + check_targeted_unary_f32::( + "asinh_u35", + &asinh_inputs, + contracts::ASINH_U35_F32_MAX_ULP, + |v| v.asinh_u35(), + f32::asinh, + ); + check_targeted_unary_f32::( + "atanh_u35", + &atanh_inputs, + contracts::ATANH_U35_F32_MAX_ULP, + |v| v.atanh_u35(), + f32::atanh, + ); +} + +simd_math_targeted_all_backends!( + f32_inverse_hyperbolic_threshold_boundaries, + run_f32_inverse_hyperbolic_threshold_boundaries +); + +fn run_f32_inverse_hyperbolic_signed_zero_semantics() { + let mut lanes = vec![0.0f32; S::Vf32::WIDTH]; + lanes[0] = -0.0; + + let input = S::Vf32::load_from_slice(&lanes); + let asinh = input.asinh_u35(); + let atanh = input.atanh_u35(); + + assert_eq!(asinh[0].to_bits(), (-0.0f32).asinh().to_bits()); + assert_eq!(atanh[0].to_bits(), (-0.0f32).atanh().to_bits()); + + if S::Vf32::WIDTH > 1 { + assert_eq!(asinh[1].to_bits(), 0.0f32.asinh().to_bits()); + assert_eq!(atanh[1].to_bits(), 0.0f32.atanh().to_bits()); + } + + let ones = S::Vf32::set1(1.0); + let acosh = ones.acosh_u35(); + assert_eq!(acosh[0].to_bits(), 1.0f32.acosh().to_bits()); +} + +fn run_f32_inverse_hyperbolic_boundary_triplets() { + let acosh_center = 1.0f32; + let acosh_inputs = [ + f32::from_bits(acosh_center.to_bits() - 2), + f32::from_bits(acosh_center.to_bits() - 1), + acosh_center, + f32::from_bits(acosh_center.to_bits() + 1), + f32::from_bits(acosh_center.to_bits() + 2), + ]; + check_targeted_unary_f32::( + "acosh_u35", + &acosh_inputs, + contracts::ACOSH_U35_F32_MAX_ULP, + |v| v.acosh_u35(), + f32::acosh, + ); + + let plus_one = 1.0f32; + let minus_one = -1.0f32; + let atanh_inputs = [ + f32::from_bits(minus_one.to_bits() - 2), + f32::from_bits(minus_one.to_bits() - 1), + minus_one, + f32::from_bits(minus_one.to_bits() + 1), + f32::from_bits(minus_one.to_bits() + 2), + f32::from_bits(plus_one.to_bits() - 2), + f32::from_bits(plus_one.to_bits() - 1), + plus_one, + f32::from_bits(plus_one.to_bits() + 1), + f32::from_bits(plus_one.to_bits() + 2), + ]; + check_targeted_unary_f32::( + "atanh_u35", + &atanh_inputs, + contracts::ATANH_U35_F32_MAX_ULP, + |v| v.atanh_u35(), + f32::atanh, + ); +} + +simd_math_targeted_all_backends!( + f32_inverse_hyperbolic_signed_zero_semantics, + run_f32_inverse_hyperbolic_signed_zero_semantics +); +simd_math_targeted_all_backends!( + f32_inverse_hyperbolic_boundary_triplets, + run_f32_inverse_hyperbolic_boundary_triplets +); + fn run_f64_inverse_hyperbolic_domain_edges() { let acosh_inputs = [ 0.0f64, @@ -271,6 +385,38 @@ simd_math_targeted_all_backends!( run_f64_inverse_hyperbolic_mixed_lanes ); +fn run_f64_inverse_hyperbolic_threshold_boundaries() { + let mut asinh_inputs = Vec::new(); + let mut acosh_inputs = Vec::new(); + + for center in [-1.0e150f64, -1.0, -0.0, 0.0, 1.0, 1.0e150] { + push_boundary_triplet_f64(&mut asinh_inputs, center); + } + for center in [1.0f64, 1.5] { + push_boundary_triplet_f64(&mut acosh_inputs, center); + } + + check_targeted_unary_f64::( + "asinh_u35", + &asinh_inputs, + contracts::ASINH_U35_F64_MAX_ULP, + |v| v.asinh_u35(), + f64::asinh, + ); + check_targeted_unary_f64::( + "acosh_u35", + &acosh_inputs, + contracts::ACOSH_U35_F64_MAX_ULP, + |v| v.acosh_u35(), + f64::acosh, + ); +} + +simd_math_targeted_all_backends!( + f64_inverse_hyperbolic_threshold_boundaries, + run_f64_inverse_hyperbolic_threshold_boundaries +); + fn run_f64_inverse_hyperbolic_signed_zero_semantics() { let mut lanes = vec![0.0f64; S::Vf64::WIDTH]; lanes[0] = -0.0; @@ -296,3 +442,48 @@ simd_math_targeted_all_backends!( f64_inverse_hyperbolic_signed_zero_semantics, run_f64_inverse_hyperbolic_signed_zero_semantics ); + +fn run_f64_inverse_hyperbolic_boundary_triplets() { + let acosh_center = 1.0f64; + let acosh_inputs = [ + f64::from_bits(acosh_center.to_bits() - 2), + f64::from_bits(acosh_center.to_bits() - 1), + acosh_center, + f64::from_bits(acosh_center.to_bits() + 1), + f64::from_bits(acosh_center.to_bits() + 2), + ]; + check_targeted_unary_f64::( + "acosh_u35", + &acosh_inputs, + contracts::ACOSH_U35_F64_MAX_ULP, + |v| v.acosh_u35(), + f64::acosh, + ); + + let plus_one = 1.0f64; + let minus_one = -1.0f64; + let atanh_inputs = [ + f64::from_bits(minus_one.to_bits() - 2), + f64::from_bits(minus_one.to_bits() - 1), + minus_one, + f64::from_bits(minus_one.to_bits() + 1), + f64::from_bits(minus_one.to_bits() + 2), + f64::from_bits(plus_one.to_bits() - 2), + f64::from_bits(plus_one.to_bits() - 1), + plus_one, + f64::from_bits(plus_one.to_bits() + 1), + f64::from_bits(plus_one.to_bits() + 2), + ]; + check_targeted_unary_f64::( + "atanh_u35", + &atanh_inputs, + contracts::ATANH_U35_F64_MAX_ULP, + |v| v.atanh_u35(), + f64::atanh, + ); +} + +simd_math_targeted_all_backends!( + f64_inverse_hyperbolic_boundary_triplets, + run_f64_inverse_hyperbolic_boundary_triplets +);