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

Commit 2fbe1e8

Browse files
committed
Create interfaces for testing against MPFR
Add a way to call MPFR versions of functions in a predictable way, using the `MpOp` trait. Everything new here is guarded by the feature `multiprecision-tests` since MPFR cannot easily build on Windows or any cross compiled targets.
1 parent f778935 commit 2fbe1e8

File tree

3 files changed

+325
-0
lines changed

3 files changed

+325
-0
lines changed

crates/libm-test/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@ default = []
1010
# Generate tests which are random inputs and the outputs are calculated with
1111
# musl libc.
1212
musl-bitwise-tests = ["rand"]
13+
multiprecision-tests = ["dep:az", "dep:rug"]
1314

1415
[dependencies]
1516
anyhow = "1.0.90"
17+
az = { version = "1.2.1", optional = true }
1618
libm = { path = "../.." }
1719
libm-macros = { path = "../libm-macros" }
1820
paste = "1.0.15"
1921
rand = "0.8.5"
2022
rand_chacha = "0.3.1"
23+
rug = { version = "1.26.1", optional = true, default-features = false, features = ["float", "std"] }
2124

2225
[target.'cfg(target_family = "wasm")'.dependencies]
2326
# Enable randomness on WASM

crates/libm-test/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
pub mod gen;
2+
#[cfg(feature = "multiprecision-tests")]
3+
pub mod mpfloat;
24
mod num_traits;
35
mod test_traits;
46

crates/libm-test/src/mpfloat.rs

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
//! Interfaces needed to support testing with multi-precision floating point numbers.
2+
//!
3+
//! Within this module, the macros create a submodule for each `libm` function. These contain
4+
//! a struct named `Operation` that implements [`MpOp`].
5+
6+
use std::ops::RemAssign;
7+
8+
use az::Az;
9+
use rug::ops::PowAssign;
10+
use rug::Assign;
11+
pub use rug::Float as MpFloat;
12+
13+
use crate::Float;
14+
15+
/// Create a multiple-precision float with the correct number of bits for a concrete float type.
16+
fn new_mpfloat<F: Float>() -> MpFloat {
17+
MpFloat::new(F::SIGNIFICAND_BITS + 1)
18+
}
19+
20+
/// Set subnormal emulation and convert to a concrete float type.
21+
fn prep_retval<F: Float>(mp: &mut MpFloat) -> F
22+
where
23+
for<'a> &'a MpFloat: az::Cast<F>,
24+
{
25+
mp.subnormalize_ieee();
26+
(&*mp).az::<F>()
27+
}
28+
29+
/// Structures that represent a float operation.
30+
///
31+
/// The struct itself should hold any context that can be reused among calls to `run` (allocated
32+
/// `MpFloat`s).
33+
pub trait MpOp {
34+
/// Inputs to the operation (concrete float types).
35+
type Input;
36+
37+
/// Outputs from the operation (concrete float types).
38+
type Output;
39+
40+
/// Create a new instance.
41+
fn new() -> Self;
42+
43+
/// Perform the operation.
44+
///
45+
/// Usually this means assigning inputs to cached floats, performing the operation, applying
46+
/// subnormal approximation, and converting the result back to concrete values.
47+
fn run(&mut self, input: Self::Input) -> Self::Output;
48+
}
49+
50+
/// Implement `MpOp` for functions with a single return value.
51+
macro_rules! impl_mp_op {
52+
// Matcher for unary functions
53+
(
54+
fn_name: $fn_name:ident,
55+
CFn: $CFn:ty,
56+
CArgs: $CArgs:ty,
57+
CRet: $CRet:ty,
58+
RustFn: fn($fty:ty,) -> $_ret:ty,
59+
RustArgs: $RustArgs:ty,
60+
RustRet: $RustRet:ty,
61+
fn_extra: $fn_name_normalized:expr,
62+
) => {
63+
paste::paste! {
64+
pub mod $fn_name {
65+
use super::*;
66+
pub struct Operation(MpFloat);
67+
68+
impl MpOp for Operation {
69+
type Input = $RustArgs;
70+
type Output = $RustRet;
71+
72+
fn new() -> Self {
73+
Self(new_mpfloat::<$fty>())
74+
}
75+
76+
fn run(&mut self, input: Self::Input) -> Self::Output {
77+
self.0.assign(input.0);
78+
self.0.[< $fn_name_normalized _mut >]();
79+
prep_retval::<Self::Output>(&mut self.0)
80+
}
81+
}
82+
}
83+
}
84+
};
85+
// Matcher for binary functions
86+
(
87+
fn_name: $fn_name:ident,
88+
CFn: $CFn:ty,
89+
CArgs: $CArgs:ty,
90+
CRet: $CRet:ty,
91+
RustFn: fn($fty:ty, $_fty2:ty,) -> $_ret:ty,
92+
RustArgs: $RustArgs:ty,
93+
RustRet: $RustRet:ty,
94+
fn_extra: $fn_name_normalized:expr,
95+
) => {
96+
paste::paste! {
97+
pub mod $fn_name {
98+
use super::*;
99+
pub struct Operation(MpFloat, MpFloat);
100+
101+
impl MpOp for Operation {
102+
type Input = $RustArgs;
103+
type Output = $RustRet;
104+
105+
fn new() -> Self {
106+
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
107+
}
108+
109+
fn run(&mut self, input: Self::Input) -> Self::Output {
110+
self.0.assign(input.0);
111+
self.1.assign(input.1);
112+
self.0.[< $fn_name_normalized _mut >](&self.1);
113+
prep_retval::<Self::Output>(&mut self.0)
114+
}
115+
}
116+
}
117+
}
118+
};
119+
// Matcher for ternary functions
120+
(
121+
fn_name: $fn_name:ident,
122+
CFn: $CFn:ty,
123+
CArgs: $CArgs:ty,
124+
CRet: $CRet:ty,
125+
RustFn: fn($fty:ty, $_fty2:ty, $_fty3:ty,) -> $_ret:ty,
126+
RustArgs: $RustArgs:ty,
127+
RustRet: $RustRet:ty,
128+
fn_extra: $fn_name_normalized:expr,
129+
) => {
130+
paste::paste! {
131+
pub mod $fn_name {
132+
use super::*;
133+
pub struct Operation(MpFloat, MpFloat, MpFloat);
134+
135+
impl MpOp for Operation {
136+
type Input = $RustArgs;
137+
type Output = $RustRet;
138+
139+
fn new() -> Self {
140+
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
141+
}
142+
143+
fn run(&mut self, input: Self::Input) -> Self::Output {
144+
self.0.assign(input.0);
145+
self.1.assign(input.1);
146+
self.2.assign(input.2);
147+
self.0.[< $fn_name_normalized _mut >](&self.1, &self.2);
148+
prep_retval::<Self::Output>(&mut self.0)
149+
}
150+
}
151+
}
152+
}
153+
};
154+
}
155+
156+
libm_macros::for_each_function! {
157+
callback: impl_mp_op,
158+
skip: [
159+
// Most of these need a manual implementation
160+
fmod, fmodf, frexp, frexpf, ilogb, ilogbf, jn, jnf, ldexp, ldexpf,
161+
lgamma_r, lgammaf_r, modf, modff, nextafter, nextafterf, pow,powf,
162+
remquo, remquof, scalbn, scalbnf, sincos, sincosf,
163+
],
164+
fn_extra: match MACRO_FN_NAME {
165+
// Remap function names that are different between mpfr and libm
166+
expm1 | expm1f => exp_m1,
167+
fabs | fabsf => abs,
168+
fdim | fdimf => positive_diff,
169+
fma | fmaf => mul_add,
170+
fmax | fmaxf => max,
171+
fmin | fminf => min,
172+
lgamma | lgammaf => ln_gamma,
173+
log | logf => ln,
174+
log1p | log1pf => ln_1p,
175+
rint | rintf => round,
176+
tgamma | tgammaf => gamma,
177+
_ => MACRO_FN_NAME_NORMALIZED
178+
}
179+
}
180+
181+
/// Some functions are difficult to do in a generic way. Implement them here.
182+
macro_rules! impl_op_for_ty {
183+
($fty:ty, $suffix:literal) => {
184+
paste::paste! {
185+
pub mod [<nextafter $suffix>] {
186+
use super::*;
187+
pub struct Operation(MpFloat, MpFloat);
188+
189+
impl MpOp for Operation {
190+
type Input = ($fty, $fty);
191+
type Output = $fty;
192+
193+
fn new() -> Self {
194+
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
195+
}
196+
197+
fn run(&mut self, input: Self::Input) -> Self::Output {
198+
self.0.assign(input.0);
199+
self.1.assign(input.1);
200+
self.0.next_toward(&self.1);
201+
prep_retval::<Self::Output>(&mut self.0)
202+
}
203+
}
204+
}
205+
206+
pub mod [<pow $suffix>] {
207+
use super::*;
208+
pub struct Operation(MpFloat, MpFloat);
209+
210+
impl MpOp for Operation {
211+
type Input = ($fty, $fty);
212+
type Output = $fty;
213+
214+
fn new() -> Self {
215+
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
216+
}
217+
218+
fn run(&mut self, input: Self::Input) -> Self::Output {
219+
self.0.assign(input.0);
220+
self.1.assign(input.1);
221+
self.0.pow_assign(&self.1);
222+
prep_retval::<Self::Output>(&mut self.0)
223+
}
224+
}
225+
}
226+
227+
pub mod [<fmod $suffix>] {
228+
use super::*;
229+
pub struct Operation(MpFloat, MpFloat);
230+
231+
impl MpOp for Operation {
232+
type Input = ($fty, $fty);
233+
type Output = $fty;
234+
235+
fn new() -> Self {
236+
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
237+
}
238+
239+
fn run(&mut self, input: Self::Input) -> Self::Output {
240+
self.0.assign(input.0);
241+
self.1.assign(input.1);
242+
self.0.rem_assign(&self.1);
243+
prep_retval::<Self::Output>(&mut self.0)
244+
}
245+
}
246+
}
247+
248+
pub mod [<lgamma_r $suffix>] {
249+
use super::*;
250+
pub struct Operation(MpFloat);
251+
252+
impl MpOp for Operation {
253+
type Input = ($fty,);
254+
type Output = ($fty, i32);
255+
256+
fn new() -> Self {
257+
Self(new_mpfloat::<$fty>())
258+
}
259+
260+
fn run(&mut self, input: Self::Input) -> Self::Output {
261+
self.0.assign(input.0);
262+
let ordering = self.0.ln_abs_gamma_mut();
263+
let ret = prep_retval::<$fty>(&mut self.0);
264+
(ret, ordering as i32)
265+
}
266+
}
267+
}
268+
269+
pub mod [<jn $suffix>] {
270+
use super::*;
271+
pub struct Operation(i32, MpFloat);
272+
273+
impl MpOp for Operation {
274+
type Input = (i32, $fty);
275+
type Output = $fty;
276+
277+
fn new() -> Self {
278+
Self(0, new_mpfloat::<$fty>())
279+
}
280+
281+
fn run(&mut self, input: Self::Input) -> Self::Output {
282+
self.0 = input.0;
283+
self.1.assign(input.1);
284+
self.1.jn_mut(self.0);
285+
prep_retval::<$fty>(&mut self.1)
286+
}
287+
}
288+
}
289+
290+
pub mod [<sincos $suffix>] {
291+
use super::*;
292+
pub struct Operation(MpFloat, MpFloat);
293+
294+
impl MpOp for Operation {
295+
type Input = ($fty,);
296+
type Output = ($fty, $fty);
297+
298+
fn new() -> Self {
299+
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
300+
}
301+
302+
fn run(&mut self, input: Self::Input) -> Self::Output {
303+
self.0.assign(input.0);
304+
self.1.assign(0.0);
305+
self.0.sin_cos_mut(&mut self.1);
306+
(prep_retval::<$fty>(&mut self.0), prep_retval::<$fty>(&mut self.1))
307+
}
308+
}
309+
}
310+
}
311+
};
312+
}
313+
314+
impl_op_for_ty!(f32, "f");
315+
impl_op_for_ty!(f64, "");
316+
317+
// Account for `lgamma_r` not having a simple `f` suffix
318+
pub mod lgammaf_r {
319+
pub use super::lgamma_rf::*;
320+
}

0 commit comments

Comments
 (0)