1
1
//! Compare the results of the `libm` implementation against the system's libm.
2
2
#![ cfg( test) ]
3
3
#![ cfg( feature = "system_libm" ) ]
4
+
5
+ use libm_test:: WithinUlps ;
6
+
4
7
// Number of tests to generate for each function
5
8
const NTESTS : usize = 500 ;
6
9
7
- // FIXME: should be 1
8
10
const ULP_TOL : usize = 4 ;
9
11
10
12
macro_rules! system_libm {
11
13
// Skip those parts of the API that are not
12
14
// exposed by the system libm library:
15
+ //
16
+ // FIXME: maybe we can skip them as part of libm-analyze?
13
17
(
14
18
id: j0f;
15
19
arg_tys: $( $arg_tys: ty) ,* ;
@@ -72,33 +76,34 @@ macro_rules! system_libm {
72
76
use crate :: Call ;
73
77
let mut rng = rand:: thread_rng( ) ;
74
78
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 {
90
84
libm:: $id( $( $arg_ids) ,* )
91
85
}
92
- let result = <( $( $arg_tys) ,* ) as Call <
93
- unsafe extern "C" fn ( $( $arg_tys) ,* ) -> $ret_ty
94
- >>:: call( args, libm_fn) ;
95
86
extern "C" {
87
+ // The system's libm function:
96
88
fn $id( $( $arg_ids: $arg_tys) ,* ) -> $ret_ty;
97
89
}
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 ) {
102
107
eprintln!( "result = {:?} != {:?} (expected)" , result, expected) ;
103
108
panic!( ) ;
104
109
}
@@ -109,13 +114,20 @@ macro_rules! system_libm {
109
114
110
115
libm_analyze:: for_each_api!( system_libm) ;
111
116
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.
112
123
trait Call < F > {
113
124
type Ret ;
114
125
fn call ( self , f : F ) -> Self :: Ret ;
115
126
}
116
127
117
128
macro_rules! impl_call {
118
129
( ( $( $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:
119
131
impl Call <unsafe extern"C" fn ( $( $arg_tys) ,* ) -> $ret_ty> for ( $( $arg_tys) ,+) {
120
132
type Ret = $ret_ty;
121
133
fn call( self , f: unsafe extern "C" fn ( $( $arg_tys) ,* ) -> $ret_ty) -> Self :: Ret {
@@ -130,17 +142,18 @@ impl_call!((f32) -> f32: x: x);
130
142
impl_call ! ( ( f64 ) -> f64 : x: x) ;
131
143
impl_call ! ( ( f64 ) -> i32 : x: x) ;
132
144
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 ) ;
136
147
impl_call ! ( ( f64 , i32 ) -> f64 : x: x. 0 , x. 1 ) ;
137
148
impl_call ! ( ( f32 , i32 ) -> f32 : x: x. 0 , x. 1 ) ;
138
149
impl_call ! ( ( i32 , f64 ) -> f64 : x: x. 0 , x. 1 ) ;
139
150
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 ) ;
140
153
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.
144
157
trait Rand {
145
158
fn gen ( rng : & mut rand:: rngs:: ThreadRng ) -> Self ;
146
159
}
@@ -149,64 +162,19 @@ macro_rules! impl_rand {
149
162
( $id: ident: [ $( $e: expr) ,* ] ) => {
150
163
impl Rand for $id {
151
164
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 {
155
168
* [ $( $e) ,* ] . choose( r) . unwrap( )
156
169
} else {
157
170
r. gen :: <$id>( )
158
- } ;
159
- unsafe { std:: mem:: transmute( r) }
171
+ }
160
172
}
161
173
}
162
174
}
163
175
}
164
176
177
+ // Some interesting random values
165
178
impl_rand ! ( f32 : [ std:: f32 :: NAN , std:: f32 :: INFINITY , std:: f32 :: NEG_INFINITY ] ) ;
166
179
impl_rand ! ( f64 : [ std:: f64 :: NAN , std:: f64 :: INFINITY , std:: f64 :: NEG_INFINITY ] ) ;
167
180
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