Skip to content
This repository was archived by the owner on Apr 28, 2025. It is now read-only.

Commit e193569

Browse files
committed
Add tests for edge cases
Introduce a generator that will tests various points of interest including zeros, infinities, and NaNs.
1 parent a114878 commit e193569

File tree

4 files changed

+101
-3
lines changed

4 files changed

+101
-3
lines changed

crates/libm-test/src/domain.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use std::fmt;
44
use std::ops::{self, Bound};
55

6-
use crate::Float;
6+
use crate::{Float, FloatExt};
77

88
/// Representation of a function's domain.
99
#[derive(Clone, Debug)]
@@ -19,7 +19,7 @@ pub struct Domain<T> {
1919

2020
type BoxIter<T> = Box<dyn Iterator<Item = T>>;
2121

22-
impl<F: Float> Domain<F> {
22+
impl<F: FloatExt> Domain<F> {
2323
/// The start of this domain, saturating at negative infinity.
2424
pub fn range_start(&self) -> F {
2525
match self.start {

crates/libm-test/src/gen.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use crate::GenerateInput;
44
pub mod domain_logspace;
5+
pub mod edge_cases;
56
pub mod random;
67

78
/// Helper type to turn any reusable input into a generator.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//! A generator that checks a handful of cases near infinities, zeros, asymptotes, and NaNs.
2+
3+
use libm::support::Float;
4+
5+
use crate::domain::HasDomain;
6+
use crate::{FloatExt, MathOp};
7+
8+
/// Number of values near an interesting point to check.
9+
// FIXME(ntests): replace this with a more logical algorithm
10+
const AROUND: usize = 100;
11+
12+
/// Functions have infinite asymptotes, limit how many we check.
13+
// FIXME(ntests): replace this with a more logical algorithm
14+
const MAX_CHECK_POINTS: usize = 10;
15+
16+
/// Create a list of values around interesting points (infinities, zeroes, NaNs).
17+
pub fn get_test_cases<Op, F>() -> impl Iterator<Item = (F,)>
18+
where
19+
Op: MathOp<FTy = F> + HasDomain<F>,
20+
F: Float,
21+
{
22+
let mut ret = Vec::new();
23+
let values = &mut ret;
24+
let domain = Op::DOMAIN;
25+
let domain_start = domain.range_start();
26+
let domain_end = domain.range_end();
27+
28+
// Check near some notable constants
29+
count_up(F::ONE, values);
30+
count_up(F::ZERO, values);
31+
count_up(F::NEG_ONE, values);
32+
count_down(F::ONE, values);
33+
count_down(F::ZERO, values);
34+
count_down(F::NEG_ONE, values);
35+
values.push(F::NEG_ZERO);
36+
37+
// Check values near the extremes
38+
count_up(F::NEG_INFINITY, values);
39+
count_down(F::INFINITY, values);
40+
count_down(domain_end, values);
41+
count_up(domain_start, values);
42+
count_down(domain_start, values);
43+
count_up(domain_end, values);
44+
count_down(domain_end, values);
45+
46+
// Check some special values that aren't included in the above ranges
47+
values.push(F::NAN);
48+
values.extend(F::consts().iter());
49+
50+
// Check around asymptotes
51+
if let Some(f) = domain.check_points {
52+
let iter = f();
53+
for x in iter.take(MAX_CHECK_POINTS) {
54+
count_up(x, values);
55+
count_down(x, values);
56+
}
57+
}
58+
59+
// Some results may overlap so deduplicate the vector to save test cycles.
60+
values.sort_by_key(|x| x.to_bits());
61+
values.dedup_by_key(|x| x.to_bits());
62+
63+
ret.into_iter().map(|v| (v,))
64+
}
65+
66+
/// Add `AROUND` values starting at and including `x` and counting up. Uses the smallest possible
67+
/// increments (1 ULP).
68+
fn count_up<F: Float>(mut x: F, values: &mut Vec<F>) {
69+
assert!(!x.is_nan());
70+
71+
let mut count = 0;
72+
while x < F::INFINITY && count < AROUND {
73+
values.push(x);
74+
x = x.next_up();
75+
count += 1;
76+
}
77+
}
78+
79+
/// Add `AROUND` values starting at and including `x` and counting down. Uses the smallest possible
80+
/// increments (1 ULP).
81+
fn count_down<F: Float>(mut x: F, values: &mut Vec<F>) {
82+
assert!(!x.is_nan());
83+
84+
let mut count = 0;
85+
while x > F::NEG_INFINITY && count < AROUND {
86+
values.push(x);
87+
x = x.next_down();
88+
count += 1;
89+
}
90+
}

crates/libm-test/tests/multiprecision.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#![cfg(feature = "test-multiprecision")]
44

55
use libm_test::domain::HasDomain;
6-
use libm_test::gen::{CachedInput, domain_logspace, random};
6+
use libm_test::gen::{CachedInput, domain_logspace, edge_cases, random};
77
use libm_test::mpfloat::MpOp;
88
use libm_test::{
99
CheckBasis, CheckCtx, CheckOutput, GenerateInput, MathOp, OpFTy, OpRustFn, OpRustRet, TupleCall,
@@ -79,6 +79,13 @@ macro_rules! mp_domain_tests {
7979
attrs: [$($meta:meta)*]
8080
) => {
8181
paste::paste! {
82+
#[test]
83+
$(#[$meta])*
84+
fn [< mp_edge_case_ $fn_name >]() {
85+
type Op = libm_test::op::$fn_name::Routine;
86+
domain_test_runner::<Op>(edge_cases::get_test_cases::<Op, _>());
87+
}
88+
8289
#[test]
8390
$(#[$meta])*
8491
fn [< mp_logspace_ $fn_name >]() {

0 commit comments

Comments
 (0)