From 13611a1b7685e4e92b8b1b85449c2a6eb915f382 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 19 Dec 2024 11:22:02 +0000 Subject: [PATCH] Add tests for edge cases Introduce a generator that will tests various points of interest including zeros, infinities, and NaNs. --- .../libm/crates/libm-test/src/domain.rs | 4 +- .../libm/crates/libm-test/src/gen.rs | 1 + .../crates/libm-test/src/gen/edge_cases.rs | 90 +++++++++++++++++++ .../crates/libm-test/tests/multiprecision.rs | 9 +- 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 library/compiler-builtins/libm/crates/libm-test/src/gen/edge_cases.rs diff --git a/library/compiler-builtins/libm/crates/libm-test/src/domain.rs b/library/compiler-builtins/libm/crates/libm-test/src/domain.rs index 43ba21974c85..9ee8a19b9709 100644 --- a/library/compiler-builtins/libm/crates/libm-test/src/domain.rs +++ b/library/compiler-builtins/libm/crates/libm-test/src/domain.rs @@ -3,7 +3,7 @@ use std::fmt; use std::ops::{self, Bound}; -use crate::Float; +use crate::{Float, FloatExt}; /// Representation of a function's domain. #[derive(Clone, Debug)] @@ -19,7 +19,7 @@ pub struct Domain { type BoxIter = Box>; -impl Domain { +impl Domain { /// The start of this domain, saturating at negative infinity. pub fn range_start(&self) -> F { match self.start { diff --git a/library/compiler-builtins/libm/crates/libm-test/src/gen.rs b/library/compiler-builtins/libm/crates/libm-test/src/gen.rs index e3c88c44a9eb..2d15915d91ae 100644 --- a/library/compiler-builtins/libm/crates/libm-test/src/gen.rs +++ b/library/compiler-builtins/libm/crates/libm-test/src/gen.rs @@ -2,6 +2,7 @@ use crate::GenerateInput; pub mod domain_logspace; +pub mod edge_cases; pub mod random; /// Helper type to turn any reusable input into a generator. diff --git a/library/compiler-builtins/libm/crates/libm-test/src/gen/edge_cases.rs b/library/compiler-builtins/libm/crates/libm-test/src/gen/edge_cases.rs new file mode 100644 index 000000000000..625e18bc7a83 --- /dev/null +++ b/library/compiler-builtins/libm/crates/libm-test/src/gen/edge_cases.rs @@ -0,0 +1,90 @@ +//! A generator that checks a handful of cases near infinities, zeros, asymptotes, and NaNs. + +use libm::support::Float; + +use crate::domain::HasDomain; +use crate::{FloatExt, MathOp}; + +/// Number of values near an interesting point to check. +// FIXME(ntests): replace this with a more logical algorithm +const AROUND: usize = 100; + +/// Functions have infinite asymptotes, limit how many we check. +// FIXME(ntests): replace this with a more logical algorithm +const MAX_CHECK_POINTS: usize = 10; + +/// Create a list of values around interesting points (infinities, zeroes, NaNs). +pub fn get_test_cases() -> impl Iterator +where + Op: MathOp + HasDomain, + F: Float, +{ + let mut ret = Vec::new(); + let values = &mut ret; + let domain = Op::DOMAIN; + let domain_start = domain.range_start(); + let domain_end = domain.range_end(); + + // Check near some notable constants + count_up(F::ONE, values); + count_up(F::ZERO, values); + count_up(F::NEG_ONE, values); + count_down(F::ONE, values); + count_down(F::ZERO, values); + count_down(F::NEG_ONE, values); + values.push(F::NEG_ZERO); + + // Check values near the extremes + count_up(F::NEG_INFINITY, values); + count_down(F::INFINITY, values); + count_down(domain_end, values); + count_up(domain_start, values); + count_down(domain_start, values); + count_up(domain_end, values); + count_down(domain_end, values); + + // Check some special values that aren't included in the above ranges + values.push(F::NAN); + values.extend(F::consts().iter()); + + // Check around asymptotes + if let Some(f) = domain.check_points { + let iter = f(); + for x in iter.take(MAX_CHECK_POINTS) { + count_up(x, values); + count_down(x, values); + } + } + + // Some results may overlap so deduplicate the vector to save test cycles. + values.sort_by_key(|x| x.to_bits()); + values.dedup_by_key(|x| x.to_bits()); + + ret.into_iter().map(|v| (v,)) +} + +/// Add `AROUND` values starting at and including `x` and counting up. Uses the smallest possible +/// increments (1 ULP). +fn count_up(mut x: F, values: &mut Vec) { + assert!(!x.is_nan()); + + let mut count = 0; + while x < F::INFINITY && count < AROUND { + values.push(x); + x = x.next_up(); + count += 1; + } +} + +/// Add `AROUND` values starting at and including `x` and counting down. Uses the smallest possible +/// increments (1 ULP). +fn count_down(mut x: F, values: &mut Vec) { + assert!(!x.is_nan()); + + let mut count = 0; + while x > F::NEG_INFINITY && count < AROUND { + values.push(x); + x = x.next_down(); + count += 1; + } +} diff --git a/library/compiler-builtins/libm/crates/libm-test/tests/multiprecision.rs b/library/compiler-builtins/libm/crates/libm-test/tests/multiprecision.rs index e643f3c9c2c2..5255dc1cfd7e 100644 --- a/library/compiler-builtins/libm/crates/libm-test/tests/multiprecision.rs +++ b/library/compiler-builtins/libm/crates/libm-test/tests/multiprecision.rs @@ -3,7 +3,7 @@ #![cfg(feature = "test-multiprecision")] use libm_test::domain::HasDomain; -use libm_test::gen::{CachedInput, domain_logspace, random}; +use libm_test::gen::{CachedInput, domain_logspace, edge_cases, random}; use libm_test::mpfloat::MpOp; use libm_test::{ CheckBasis, CheckCtx, CheckOutput, GenerateInput, MathOp, OpFTy, OpRustFn, OpRustRet, TupleCall, @@ -79,6 +79,13 @@ macro_rules! mp_domain_tests { attrs: [$($meta:meta)*] ) => { paste::paste! { + #[test] + $(#[$meta])* + fn [< mp_edge_case_ $fn_name >]() { + type Op = libm_test::op::$fn_name::Routine; + domain_test_runner::(edge_cases::get_test_cases::()); + } + #[test] $(#[$meta])* fn [< mp_logspace_ $fn_name >]() {