Add tests for edge cases

Introduce a generator that will tests various points of interest
including zeros, infinities, and NaNs.
This commit is contained in:
Trevor Gross
2024-12-19 11:22:02 +00:00
parent a8a2f70ae6
commit 13611a1b76
4 changed files with 101 additions and 3 deletions

View File

@@ -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<T> {
type BoxIter<T> = Box<dyn Iterator<Item = T>>;
impl<F: Float> Domain<F> {
impl<F: FloatExt> Domain<F> {
/// The start of this domain, saturating at negative infinity.
pub fn range_start(&self) -> F {
match self.start {

View File

@@ -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.

View File

@@ -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<Op, F>() -> impl Iterator<Item = (F,)>
where
Op: MathOp<FTy = F> + HasDomain<F>,
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<F: Float>(mut x: F, values: &mut Vec<F>) {
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<F: Float>(mut x: F, values: &mut Vec<F>) {
assert!(!x.is_nan());
let mut count = 0;
while x > F::NEG_INFINITY && count < AROUND {
values.push(x);
x = x.next_down();
count += 1;
}
}