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

Commit 7d73ae1

Browse files
committed
Document and refactor libm-test a bit
1 parent e272698 commit 7d73ae1

File tree

2 files changed

+98
-79
lines changed

2 files changed

+98
-79
lines changed

crates/libm-test/src/lib.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//! Testing utilities required by most tests.
2+
3+
pub trait WithinUlps {
4+
/// Returns true if two numbers are closer than `ulp_tol` to each other.
5+
fn within_ulps(self, other: Self, ulp_tol: usize) -> bool;
6+
}
7+
8+
// TODO: this should be moved to the libm-test/src/lib.rs library. And we
9+
// should make ulps configurable.
10+
11+
// Stamp the impls for floats:
12+
macro_rules! impl_within_ulps_f {
13+
($f_ty:ty, $i_ty:ty) => {
14+
impl WithinUlps for $f_ty {
15+
fn within_ulps(self, y: $f_ty, ulp_tol: usize) -> bool {
16+
let x = self;
17+
if x.is_nan() != y.is_nan() {
18+
// one is nan but the other is not
19+
return false;
20+
}
21+
if x.is_nan() && y.is_nan() {
22+
return true;
23+
}
24+
if x.is_infinite() != y.is_infinite() {
25+
// one is inf but the other is not
26+
return false;
27+
}
28+
29+
let xi: $i_ty = unsafe { core::intrinsics::transmute(x) };
30+
let yi: $i_ty = unsafe { core::intrinsics::transmute(y) };
31+
if (xi < 0) != (yi < 0) {
32+
// different sign, e.g., -0.0 != +0.0:
33+
return false;
34+
}
35+
let ulps = (xi - yi).abs();
36+
ulps <= ulp_tol as _
37+
}
38+
}
39+
};
40+
}
41+
42+
impl_within_ulps_f!(f32, i32);
43+
impl_within_ulps_f!(f64, i64);
44+
45+
impl WithinUlps for i32 {
46+
fn within_ulps(self, y: i32, ulp_tol: usize) -> bool {
47+
let x = self;
48+
let ulps = (x - y).abs();
49+
ulps <= ulp_tol as _
50+
}
51+
}

crates/libm-test/tests/system_libm.rs

Lines changed: 47 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
//! Compare the results of the `libm` implementation against the system's libm.
22
#![cfg(test)]
33
#![cfg(feature = "system_libm")]
4+
5+
use libm_test::WithinUlps;
6+
47
// Number of tests to generate for each function
58
const NTESTS: usize = 500;
69

7-
// FIXME: should be 1
810
const ULP_TOL: usize = 4;
911

1012
macro_rules! system_libm {
1113
// Skip those parts of the API that are not
1214
// exposed by the system libm library:
15+
//
16+
// FIXME: maybe we can skip them as part of libm-analyze?
1317
(
1418
id: j0f;
1519
arg_tys: $($arg_tys:ty),*;
@@ -72,33 +76,34 @@ macro_rules! system_libm {
7276
use crate::Call;
7377
let mut rng = rand::thread_rng();
7478
for _ in 0..NTESTS {
75-
let mut args: ( $($arg_tys),+ ) = ( $(<$arg_tys as Rand>::gen(&mut rng)),+ );
76-
77-
match stringify!($id) {
78-
"j1" | "jn" => {
79-
// First argument to this function appears to be a number of
80-
// iterations, so passing in massive random numbers causes it to
81-
// take forever to execute, so make sure we're not running random
82-
// math code until the heat death of the universe.
83-
let p = &mut args as *mut _ as *mut i32;
84-
unsafe { p.write(p.read() & 0xffff) }
85-
},
86-
_ => (),
87-
}
88-
89-
unsafe extern "C" fn libm_fn($($arg_ids: $arg_tys),*) -> $ret_ty {
79+
// Type of the system libm fn:
80+
type FnTy = unsafe extern "C" fn ($($arg_ids: $arg_tys),*) -> $ret_ty;
81+
// FIXME: extern "C" wrapper over our libm functions
82+
// Shouldn't be needed once they are all extern "C"
83+
extern "C" fn libm_fn($($arg_ids: $arg_tys),*) -> $ret_ty {
9084
libm::$id($($arg_ids),*)
9185
}
92-
let result = <($($arg_tys),*) as Call<
93-
unsafe extern "C" fn($($arg_tys),*) -> $ret_ty
94-
>>::call(args, libm_fn);
9586
extern "C" {
87+
// The system's libm function:
9688
fn $id($($arg_ids: $arg_tys),*) -> $ret_ty;
9789
}
98-
let expected = <($($arg_tys),*) as Call<
99-
unsafe extern "C" fn($($arg_tys),*) -> $ret_ty
100-
>>::call(args, $id);
101-
if !result.eq(expected) {
90+
91+
// Generate a tuple of arguments containing random values:
92+
let mut args: ( $($arg_tys),+ ) = ( $(<$arg_tys as Rand>::gen(&mut rng)),+ );
93+
94+
// HACK
95+
if let "j1" | "jn" = stringify!($id) {
96+
// First argument to these functions are a number of
97+
// iterations and passing large random numbers takes forever
98+
// to execute, so check if their higher bits are set and
99+
// zero them:
100+
let p = &mut args as *mut _ as *mut i32;
101+
unsafe { p.write(p.read() & 0xffff) }
102+
}
103+
104+
let result = args.call(libm_fn as FnTy);
105+
let expected = args.call($id as FnTy);
106+
if !result.within_ulps(expected, ULP_TOL) {
102107
eprintln!("result = {:?} != {:?} (expected)", result, expected);
103108
panic!();
104109
}
@@ -109,13 +114,20 @@ macro_rules! system_libm {
109114

110115
libm_analyze::for_each_api!(system_libm);
111116

117+
// This implements function dispatch for tuples of arguments used in the tests
118+
// above, so that we can: (f32, 32).call(fn(f32, f32) -> f32) generically.
119+
//
120+
// We need the input parameter F to support dispatching, e.g., (f32,f32) with
121+
// functions that return both f32 or i32. Those are two different types, so we
122+
// need to be parametric over them.
112123
trait Call<F> {
113124
type Ret;
114125
fn call(self, f: F) -> Self::Ret;
115126
}
116127

117128
macro_rules! impl_call {
118129
(($($arg_tys:ty),*) -> $ret_ty:ty: $self_:ident: $($xs:expr),*) => {
130+
// We only care about unsafe extern "C" functions here, safe functions coerce to them:
119131
impl Call<unsafe extern"C" fn($($arg_tys),*) -> $ret_ty> for ($($arg_tys),+) {
120132
type Ret = $ret_ty;
121133
fn call(self, f: unsafe extern "C" fn($($arg_tys),*) -> $ret_ty) -> Self::Ret {
@@ -130,17 +142,18 @@ impl_call!((f32) -> f32: x: x);
130142
impl_call!((f64) -> f64: x: x);
131143
impl_call!((f64) -> i32: x: x);
132144
impl_call!((f32) -> i32: x: x);
133-
134-
impl_call!((f32,f32) -> f32: x: x.0, x.1);
135-
impl_call!((f64,f64) -> f64: x: x.0, x.1);
145+
impl_call!((f32, f32) -> f32: x: x.0, x.1);
146+
impl_call!((f64, f64) -> f64: x: x.0, x.1);
136147
impl_call!((f64, i32) -> f64: x: x.0, x.1);
137148
impl_call!((f32, i32) -> f32: x: x.0, x.1);
138149
impl_call!((i32, f64) -> f64: x: x.0, x.1);
139150
impl_call!((i32, f32) -> f32: x: x.0, x.1);
151+
impl_call!((f32, f32, f32) -> f32: x: x.0, x.1, x.2);
152+
impl_call!((f64, f64, f64) -> f64: x: x.0, x.1, x.2);
140153

141-
impl_call!((f32,f32,f32) -> f32: x: x.0, x.1, x.2);
142-
impl_call!((f64,f64,f64) -> f64: x: x.0, x.1, x.2);
143-
154+
// We need to be able to generate random numbers for the types involved.
155+
//
156+
// Rand does this well, but we want to also test some other specific values.
144157
trait Rand {
145158
fn gen(rng: &mut rand::rngs::ThreadRng) -> Self;
146159
}
@@ -149,64 +162,19 @@ macro_rules! impl_rand {
149162
($id:ident: [$($e:expr),*]) => {
150163
impl Rand for $id {
151164
fn gen(r: &mut rand::rngs::ThreadRng) -> Self {
152-
use rand::Rng;
153-
use rand::seq::SliceRandom;
154-
let r = if r.gen_range(0, 20) < 1 {
165+
use rand::{Rng, seq::SliceRandom};
166+
// 1/20 probability of picking a non-random value
167+
if r.gen_range(0, 20) < 1 {
155168
*[$($e),*].choose(r).unwrap()
156169
} else {
157170
r.gen::<$id>()
158-
};
159-
unsafe { std::mem::transmute(r) }
171+
}
160172
}
161173
}
162174
}
163175
}
164176

177+
// Some interesting random values
165178
impl_rand!(f32: [std::f32::NAN, std::f32::INFINITY, std::f32::NEG_INFINITY]);
166179
impl_rand!(f64: [std::f64::NAN, std::f64::INFINITY, std::f64::NEG_INFINITY]);
167180
impl_rand!(i32: [i32::max_value(), 0_i32, i32::min_value()]);
168-
169-
trait Equal {
170-
fn eq(self, other: Self) -> bool;
171-
}
172-
173-
macro_rules! impl_eq_f {
174-
($f_ty:ty, $i_ty:ty) => {
175-
impl Equal for $f_ty {
176-
fn eq(self, y: $f_ty) -> bool {
177-
let x = self;
178-
if x.is_nan() != y.is_nan() {
179-
// one is nan but the other is not
180-
return false;
181-
}
182-
if x.is_nan() && y.is_nan() {
183-
return true;
184-
}
185-
if x.is_infinite() != y.is_infinite() {
186-
// one is inf but the other is not
187-
return false;
188-
}
189-
190-
let xi: $i_ty = unsafe { core::intrinsics::transmute(x) };
191-
let yi: $i_ty = unsafe { core::intrinsics::transmute(y) };
192-
if (xi < 0) != (yi < 0) {
193-
// different sign
194-
return false;
195-
}
196-
let ulps = (xi - yi).abs();
197-
ulps <= ULP_TOL as _
198-
}
199-
}
200-
};
201-
}
202-
203-
impl_eq_f!(f32, i32);
204-
impl_eq_f!(f64, i64);
205-
206-
impl Equal for i32 {
207-
fn eq(self, y: i32) -> bool {
208-
let x = self;
209-
let ulps = (x - y).abs();
210-
ulps <= 1
211-
}
212-
}

0 commit comments

Comments
 (0)