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

Commit 0d3765d

Browse files
committed
Add a test against MPFR using random inputs
1 parent c4aeec2 commit 0d3765d

File tree

5 files changed

+163
-8
lines changed

5 files changed

+163
-8
lines changed

crates/libm-test/build.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ fn emit_cfg_shorthands(cfg: &Config) {
6464
// Shorthand to detect i586 targets
6565
println!("cargo::rustc-cfg=x86_no_sse");
6666
}
67+
68+
println!("cargo::rustc-check-cfg=cfg(x86_macos)");
69+
if cfg.target_arch == "x86_64" && cfg.target_vendor == "apple" {
70+
println!("cargo::rustc-cfg=x86_macos");
71+
}
6772
}
6873

6974
/// Create a list of all source files in an array. This can be used for making sure that

crates/libm-test/src/lib.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,18 @@ pub type TestResult<T = (), E = anyhow::Error> = Result<T, E>;
1616
// List of all files present in libm's source
1717
include!(concat!(env!("OUT_DIR"), "/all_files.rs"));
1818

19-
/// ULP allowed to differ from musl (note that musl itself may not be accurate).
19+
/// Default ULP allowed to differ from musl (note that musl itself may not be accurate).
2020
const MUSL_DEFAULT_ULP: u32 = 2;
2121

22-
/// Certain functions have different allowed ULP (consider these xfail).
22+
/// Default ULP allowed to differ from multiprecision (i.e. infinite) results.
23+
const MULTIPREC_DEFAULT_ULP: u32 = 1;
24+
25+
/// ULP allowed to differ from muls results.
2326
///
2427
/// Note that these results were obtained using 400,000,000 rounds of random inputs, which
2528
/// is not a value used by default.
2629
pub fn musl_allowed_ulp(name: &str) -> u32 {
30+
// Consider overrides xfail
2731
match name {
2832
#[cfg(x86_no_sse)]
2933
"asinh" | "asinhf" => 6,
@@ -44,6 +48,22 @@ pub fn musl_allowed_ulp(name: &str) -> u32 {
4448
}
4549
}
4650

51+
/// ULP allowed to differ from multiprecision results.
52+
pub fn multiprec_allowed_ulp(name: &str) -> u32 {
53+
// Consider overrides xfail
54+
match name {
55+
"asinh" | "asinhf" => 2,
56+
"atanh" | "atanhf" => 2,
57+
"exp10" | "exp10f" => 3,
58+
"j0" | "j0f" => 2,
59+
"lgamma" | "lgammaf" | "lgamma_r" | "lgammaf_r" => 2,
60+
"sinh" | "sinhf" => 2,
61+
"tanh" | "tanhf" => 2,
62+
"tgamma" => 6,
63+
_ => MULTIPREC_DEFAULT_ULP,
64+
}
65+
}
66+
4767
/// Return the unsuffixed version of a function name; e.g. `abs` and `absf` both return `abs`,
4868
/// `lgamma_r` and `lgammaf_r` both return `lgamma_r`.
4969
pub fn canonical_name(name: &str) -> &str {

crates/libm-test/src/special_case.rs

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,25 @@ impl MaybeOverride<(f32,)> for SpecialCase {
9191

9292
maybe_check_nan_bits(actual, expected, ctx)
9393
}
94+
95+
fn check_int<I: Int>(
96+
input: (f32,),
97+
actual: I,
98+
expected: I,
99+
ctx: &CheckCtx,
100+
) -> Option<anyhow::Result<()>> {
101+
// On MPFR for lgammaf_r, we set -1 as the integer result for negative infinity but MPFR
102+
// sets +1
103+
if ctx.basis == CheckBasis::MultiPrecision
104+
&& ctx.fname == "lgammaf_r"
105+
&& input.0 == f32::NEG_INFINITY
106+
&& actual.abs() == expected.abs()
107+
{
108+
XFAIL
109+
} else {
110+
None
111+
}
112+
}
94113
}
95114

96115
impl MaybeOverride<(f64,)> for SpecialCase {
@@ -126,6 +145,25 @@ impl MaybeOverride<(f64,)> for SpecialCase {
126145

127146
maybe_check_nan_bits(actual, expected, ctx)
128147
}
148+
149+
fn check_int<I: Int>(
150+
input: (f64,),
151+
actual: I,
152+
expected: I,
153+
ctx: &CheckCtx,
154+
) -> Option<anyhow::Result<()>> {
155+
// On MPFR for lgamma_r, we set -1 as the integer result for negative infinity but MPFR
156+
// sets +1
157+
if ctx.basis == CheckBasis::MultiPrecision
158+
&& ctx.fname == "lgamma_r"
159+
&& input.0 == f64::NEG_INFINITY
160+
&& actual.abs() == expected.abs()
161+
{
162+
XFAIL
163+
} else {
164+
None
165+
}
166+
}
129167
}
130168

131169
/// Check NaN bits if the function requires it
@@ -185,23 +223,51 @@ fn maybe_skip_min_max_nan<F1: Float, F2: Float>(
185223
impl MaybeOverride<(i32, f32)> for SpecialCase {
186224
fn check_float<F: Float>(
187225
input: (i32, f32),
188-
_actual: F,
189-
_expected: F,
226+
actual: F,
227+
expected: F,
190228
ulp: &mut u32,
191229
ctx: &CheckCtx,
192230
) -> Option<TestResult> {
193-
bessel_prec_dropoff(input, ulp, ctx)
231+
match ctx.basis {
232+
CheckBasis::Musl => bessel_prec_dropoff(input, ulp, ctx),
233+
CheckBasis::MultiPrecision => {
234+
// We return +0.0, MPFR returns -0.0
235+
if ctx.fname == "jnf"
236+
&& input.1 == f32::NEG_INFINITY
237+
&& actual == F::ZERO
238+
&& expected == F::ZERO
239+
{
240+
XFAIL
241+
} else {
242+
None
243+
}
244+
}
245+
}
194246
}
195247
}
196248
impl MaybeOverride<(i32, f64)> for SpecialCase {
197249
fn check_float<F: Float>(
198250
input: (i32, f64),
199-
_actual: F,
200-
_expected: F,
251+
actual: F,
252+
expected: F,
201253
ulp: &mut u32,
202254
ctx: &CheckCtx,
203255
) -> Option<TestResult> {
204-
bessel_prec_dropoff(input, ulp, ctx)
256+
match ctx.basis {
257+
CheckBasis::Musl => bessel_prec_dropoff(input, ulp, ctx),
258+
CheckBasis::MultiPrecision => {
259+
// We return +0.0, MPFR returns -0.0
260+
if ctx.fname == "jn"
261+
&& input.1 == f64::NEG_INFINITY
262+
&& actual == F::ZERO
263+
&& expected == F::ZERO
264+
{
265+
XFAIL
266+
} else {
267+
None
268+
}
269+
}
270+
}
205271
}
206272
}
207273

crates/libm-test/src/test_traits.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ impl CheckCtx {
5252
pub enum CheckBasis {
5353
/// Check against Musl's math sources.
5454
Musl,
55+
/// Check against infinite precision (MPFR).
56+
MultiPrecision,
5557
}
5658

5759
/// A trait to implement on any output type so we can verify it in a generic way.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//! Test with "infinite precision"
2+
3+
#![cfg(feature = "test-multiprecision")]
4+
5+
use libm_test::gen::random;
6+
use libm_test::mpfloat::{self, MpOp};
7+
use libm_test::{CheckBasis, CheckCtx, CheckOutput, TupleCall, multiprec_allowed_ulp};
8+
9+
/// Implement a test against MPFR with random inputs.
10+
macro_rules! multiprec_rand_tests {
11+
(
12+
fn_name: $fn_name:ident,
13+
CFn: $CFn:ty,
14+
CArgs: $CArgs:ty,
15+
CRet: $CRet:ty,
16+
RustFn: $RustFn:ty,
17+
RustArgs: $RustArgs:ty,
18+
RustRet: $RustRet:ty,
19+
attrs: [$($meta:meta)*]
20+
) => {
21+
paste::paste! {
22+
#[test]
23+
$(#[$meta])*
24+
fn [< multiprec_random_ $fn_name >]() {
25+
type MpOpTy = mpfloat::$fn_name::Operation;
26+
27+
let fname = stringify!($fn_name);
28+
let ulp = multiprec_allowed_ulp(fname);
29+
let cases = random::get_test_cases::<$RustArgs>(fname);
30+
let mut mp_vals = MpOpTy::new();
31+
let ctx = CheckCtx::new(ulp, fname, CheckBasis::MultiPrecision);
32+
33+
for input in cases {
34+
let mp_res = mp_vals.run(input);
35+
let crate_res = input.call(libm::$fn_name as $RustFn);
36+
37+
mp_res.validate(crate_res, input, &ctx).unwrap();
38+
}
39+
}
40+
}
41+
};
42+
}
43+
44+
libm_macros::for_each_function! {
45+
callback: multiprec_rand_tests,
46+
attributes: [],
47+
skip: [
48+
// FIXME: MPFR tests needed
49+
frexp,
50+
frexpf,
51+
ilogb,
52+
ilogbf,
53+
ldexp,
54+
ldexpf,
55+
modf,
56+
modff,
57+
remquo,
58+
remquof,
59+
scalbn,
60+
scalbnf,
61+
],
62+
}

0 commit comments

Comments
 (0)