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

Commit 41b817d

Browse files
committed
Refactor random tests into lib; be more exhaustive at boundaries.
1 parent 7ef6fc2 commit 41b817d

File tree

5 files changed

+171
-65
lines changed

5 files changed

+171
-65
lines changed

crates/libm-bench/benches/bench.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![feature(test)]
22
extern crate test;
33

4-
use libm_test::{adjust_input, Call};
4+
use libm_test::{get_api_kind, ApiKind, Call};
55
use rand::Rng;
66
use test::Bencher;
77

@@ -29,7 +29,10 @@ macro_rules! bench_fn {
2929
let mut rng = rand::thread_rng();
3030
let mut x: ( $($arg_tys,)+ ) = ( $(rng.gen::<$arg_tys>(),)+ );
3131

32-
adjust_input!(fn: $id, input: x);
32+
if let ApiKind::Jx = get_api_kind!(fn: $id) {
33+
let ptr = &mut x as *mut _ as *mut i32;
34+
unsafe { ptr.write(ptr.read() & 0xffff) };
35+
}
3336

3437
bh.iter(|| test::black_box(x).call(libm::$id as FnTy))
3538
}

crates/libm-test/Cargo.toml

+5-3
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ version = "0.1.0"
44
authors = ["Gonzalo Brito Gadeschi <[email protected]>"]
55
edition = "2018"
66

7-
[dev-dependencies]
7+
[dependencies]
8+
rand = "0.7"
89
libm = { path = "../libm", default-features = false }
10+
11+
[dev-dependencies]
912
libm-analyze = { path = "../libm-analyze", default-features = true }
10-
rand = "0.7"
1113

1214
[features]
1315
default = []
1416
checked = ["libm/checked"]
1517
stable = ["libm/stable"]
1618
system_libm = []
17-
exhaustive = []
19+
exhaustive = []

crates/libm-test/src/lib.rs

+142-14
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ impl_call!((f64) -> f64: x: x.0);
7777
impl_call!((f64) -> i32: x: x.0);
7878
impl_call!((f32) -> i32: x: x.0);
7979
impl_call!((f32, f32) -> f32: x: x.0, x.1);
80+
impl_call!((f32, f64) -> f32: x: x.0, x.1);
8081
impl_call!((f64, f64) -> f64: x: x.0, x.1);
8182
impl_call!((f64, i32) -> f64: x: x.0, x.1);
8283
impl_call!((f32, i32) -> f32: x: x.0, x.1);
@@ -85,23 +86,53 @@ impl_call!((i32, f32) -> f32: x: x.0, x.1);
8586
impl_call!((f32, f32, f32) -> f32: x: x.0, x.1, x.2);
8687
impl_call!((f64, f64, f64) -> f64: x: x.0, x.1, x.2);
8788

88-
// Adjust the input of a function.
89+
pub trait TupleVec {
90+
type Output;
91+
fn get(&self, i: usize) -> Self::Output;
92+
}
93+
94+
macro_rules! impl_tuple_vec {
95+
(($($arg_tys:ty),*): $self_:ident: $($xs:expr),*) => {
96+
impl TupleVec for ($(Vec<$arg_tys>,)+) {
97+
type Output = ($($arg_tys,)+);
98+
fn get(&self, i: usize) -> Self::Output {
99+
let $self_ = self;
100+
($($xs[i],)*)
101+
}
102+
}
103+
};
104+
}
105+
106+
impl_tuple_vec!((f32): x: x.0);
107+
impl_tuple_vec!((f64): x: x.0);
108+
impl_tuple_vec!((f32, f32): x: x.0, x.1);
109+
impl_tuple_vec!((f32, f64): x: x.0, x.1);
110+
impl_tuple_vec!((f64, f64): x: x.0, x.1);
111+
impl_tuple_vec!((f64, i32): x: x.0, x.1);
112+
impl_tuple_vec!((f32, i32): x: x.0, x.1);
113+
impl_tuple_vec!((i32, f64): x: x.0, x.1);
114+
impl_tuple_vec!((i32, f32): x: x.0, x.1);
115+
impl_tuple_vec!((f32, f32, f32): x: x.0, x.1, x.2);
116+
impl_tuple_vec!((f64, f64, f64): x: x.0, x.1, x.2);
117+
118+
/// Kind of LibmApi - used to handle generating tests
119+
/// for some functions slightly differently.
120+
#[derive(Copy, Clone, Debug, PartialEq)]
121+
pub enum ApiKind {
122+
Jx,
123+
Other,
124+
}
125+
89126
#[macro_export]
90-
macro_rules! adjust_input {
91-
(fn: j1, input: $arg:ident) => {
92-
adjust_input!(adjust: $arg)
127+
macro_rules! get_api_kind {
128+
(fn: j1) => {
129+
$crate::ApiKind::Jx
93130
};
94-
(fn: jn, input: $arg:ident) => {
95-
adjust_input!(adjust: $arg)
131+
(fn: jn) => {
132+
$crate::ApiKind::Jx
96133
};
97-
(fn: $id:ident, input: $args:ident) => {};
98-
(adjust: $arg:ident) => {
99-
// First argument to these functions are a number of
100-
// iterations and passing large random numbers takes forever
101-
// to execute, so check if their higher bits are set and
102-
// zero them:
103-
let p = &mut $arg as *mut _ as *mut i32;
104-
unsafe { p.write(p.read() & 0xffff) }
134+
(fn: $id:ident) => {
135+
$crate::ApiKind::Other
105136
};
106137
}
107138

@@ -121,3 +152,100 @@ macro_rules! assert_approx_eq {
121152
}
122153
};
123154
}
155+
156+
pub trait Toward: Sized {
157+
fn toward(self, other: Self, len: usize) -> Vec<Self>;
158+
}
159+
160+
macro_rules! impl_toward_f {
161+
($float_ty:ident, $toward_fn:path) => {
162+
impl Toward for $float_ty {
163+
fn toward(self, other: Self, len: usize) -> Vec<Self> {
164+
let mut vec = Vec::with_capacity(len);
165+
let mut current = self;
166+
vec.push(self);
167+
for _ in 0..=len {
168+
current = $toward_fn(current, other as _);
169+
vec.push(self);
170+
if current.to_bits() == other.to_bits() {
171+
break;
172+
}
173+
}
174+
vec
175+
}
176+
}
177+
};
178+
}
179+
impl_toward_f!(f32, libm::nextafterf);
180+
impl_toward_f!(f64, libm::nextafter);
181+
182+
pub trait RandSeq: Sized {
183+
fn rand_seq<R: rand::Rng>(rng: &mut R, api_kind: ApiKind, len: usize) -> Vec<Self>;
184+
}
185+
186+
macro_rules! impl_rand_seq_f {
187+
($float_ty:ident) => {
188+
impl RandSeq for $float_ty {
189+
fn rand_seq<R: rand::Rng>(rng: &mut R, _api_kind: ApiKind, len: usize) -> Vec<Self> {
190+
use std::$float_ty::*;
191+
let mut vec = Vec::with_capacity(len);
192+
193+
// These inputs are always tested
194+
const BOUNDS: [$float_ty; 9] = [
195+
NAN,
196+
INFINITY,
197+
NEG_INFINITY,
198+
EPSILON,
199+
-EPSILON,
200+
MAX,
201+
MIN,
202+
MIN_POSITIVE,
203+
-MIN_POSITIVE,
204+
];
205+
vec.extend(&BOUNDS);
206+
// A range around the inputs is also always tested:
207+
const NSTEPS: usize = 1_000;
208+
vec.extend(INFINITY.toward(0., NSTEPS));
209+
vec.extend(NEG_INFINITY.toward(0., NSTEPS));
210+
vec.extend((0. as $float_ty).toward(MIN_POSITIVE, NSTEPS));
211+
vec.extend((0. as $float_ty).toward(-MIN_POSITIVE, NSTEPS));
212+
213+
for i in 0..=NSTEPS {
214+
let dx = 2. / NSTEPS as $float_ty;
215+
let next = (-1. as $float_ty) + (i as $float_ty) * dx;
216+
vec.push(next);
217+
}
218+
219+
// ~NSTEPS * 4
220+
assert!(len > 2 * 4 * NSTEPS, "len {} !> {}", len, 2 * 4 * NSTEPS);
221+
let current_len = vec.len();
222+
let remaining_len = len.checked_sub(current_len).unwrap();
223+
224+
for _ in 0..remaining_len {
225+
let n = rng.gen::<$float_ty>();
226+
vec.push(n);
227+
}
228+
assert_eq!(vec.len(), len);
229+
vec
230+
}
231+
}
232+
};
233+
}
234+
235+
impl_rand_seq_f!(f32);
236+
impl_rand_seq_f!(f64);
237+
238+
impl RandSeq for i32 {
239+
fn rand_seq<R: rand::Rng>(rng: &mut R, api_kind: ApiKind, len: usize) -> Vec<Self> {
240+
let mut v = Vec::with_capacity(len);
241+
for _ in 0..len {
242+
let mut r = rng.gen::<i32>();
243+
if let ApiKind::Jx = api_kind {
244+
r &= 0xffff;
245+
}
246+
v.push(r);
247+
}
248+
assert_eq!(v.len(), len);
249+
v
250+
}
251+
}

crates/libm-test/tests/system_libm.rs

+18-45
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
#![cfg(test)]
33
#![cfg(feature = "system_libm")]
44

5-
use libm_test::{adjust_input, assert_approx_eq, Call};
5+
use libm_test::{assert_approx_eq, get_api_kind, Call, RandSeq, TupleVec};
66

77
// Number of tests to generate for each function
8-
const NTESTS: usize = 500;
8+
const NTESTS: usize = 10_000;
99

1010
const ULP_TOL: usize = 4;
1111

@@ -74,25 +74,26 @@ macro_rules! system_libm {
7474
#[allow(unused)]
7575
fn $id() {
7676
use crate::Call;
77-
let mut rng = rand::thread_rng();
78-
for _ in 0..NTESTS {
79-
// Type of the system libm fn:
80-
type FnTy
81-
= unsafe extern "C" fn ($($arg_ids: $arg_tys),*) -> $ret_ty;
77+
// Type of the system libm fn:
78+
type FnTy
79+
= unsafe extern "C" fn ($($arg_ids: $arg_tys),*) -> $ret_ty;
80+
extern "C" {
81+
// The system's libm function:
82+
fn $id($($arg_ids: $arg_tys),*) -> $ret_ty;
83+
}
8284

83-
extern "C" {
84-
// The system's libm function:
85-
fn $id($($arg_ids: $arg_tys),*) -> $ret_ty;
86-
}
85+
let mut rng = rand::thread_rng();
8786

88-
// Generate a tuple of arguments containing random values:
89-
let mut args: ( $($arg_tys,)+ )
90-
= ( $(<$arg_tys as Rand>::gen(&mut rng),)+ );
87+
// Depending on the type of API, different ranges of values might be
88+
// allowed or interesting to test:
89+
let api_kind = get_api_kind!(fn: $id);
9190

92-
// Some APIs need their inputs to be "adjusted" (see macro):
93-
// correct_input!(fn: $id, input: args);
94-
adjust_input!(fn: $id, input: args);
91+
// Generate a tuple of arguments containing random values:
92+
let mut args: ( $(Vec<$arg_tys>,)+ )
93+
= ( $(<$arg_tys as RandSeq>::rand_seq(&mut rng, api_kind, NTESTS),)+ );
9594

95+
for i in 0..NTESTS {
96+
let args: ( $($arg_tys,)+ ) = args.get(i);
9697
let result = args.call(libm::$id as FnTy);
9798
let expected = args.call($id as FnTy);
9899
assert_approx_eq!(
@@ -105,31 +106,3 @@ macro_rules! system_libm {
105106
}
106107

107108
libm_analyze::for_each_api!(system_libm);
108-
109-
// We need to be able to generate random numbers for the types involved.
110-
//
111-
// Rand does this well, but we want to also test some other specific values.
112-
trait Rand {
113-
fn gen(rng: &mut rand::rngs::ThreadRng) -> Self;
114-
}
115-
116-
macro_rules! impl_rand {
117-
($id:ident: [$($e:expr),*]) => {
118-
impl Rand for $id {
119-
fn gen(r: &mut rand::rngs::ThreadRng) -> Self {
120-
use rand::{Rng, seq::SliceRandom};
121-
// 1/20 probability of picking a non-random value
122-
if r.gen_range(0, 20) < 1 {
123-
*[$($e),*].choose(r).unwrap()
124-
} else {
125-
r.gen::<$id>()
126-
}
127-
}
128-
}
129-
}
130-
}
131-
132-
// Some interesting random values
133-
impl_rand!(f32: [std::f32::NAN, std::f32::INFINITY, std::f32::NEG_INFINITY]);
134-
impl_rand!(f64: [std::f64::NAN, std::f64::INFINITY, std::f64::NEG_INFINITY]);
135-
impl_rand!(i32: [i32::max_value(), 0_i32, i32::min_value()]);

crates/libm/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! libm in pure Rust
22
#![deny(warnings)]
3-
#![no_std]
3+
//#![no_std]
44
#![cfg_attr(
55
all(target_arch = "wasm32", not(feature = "stable")),
66
feature(core_intrinsics)

0 commit comments

Comments
 (0)