|
| 1 | +//! Compare our implementations with the result of musl functions, as provided by `musl-math-sys`. |
| 2 | +//! |
| 3 | +//! Currently this only tests randomized inputs. In the future this may be improved to test edge |
| 4 | +//! cases or run exhaustive tests. |
| 5 | +//! |
| 6 | +//! Note that musl functions do not always provide 0.5ULP rounding, so our functions can do better |
| 7 | +//! than these results. |
| 8 | +
|
| 9 | +// Targets that we can't compile musl for |
| 10 | +#![cfg(not(any(target_env = "msvc", target_family = "wasm")))] |
| 11 | +// These wind up with stack overflows |
| 12 | +#![cfg(not(all(target_family = "windows", target_env = "gnu")))] |
| 13 | +// FIXME(#309): LE PPC crashes calling the musl version of some of these and are disabled. It |
| 14 | +// seems like a qemu bug but should be investigated further at some point. See |
| 15 | +// <https://github.com/rust-lang/libm/issues/309>. |
| 16 | +#![cfg(not(all(target_arch = "powerpc64", target_endian = "little")))] |
| 17 | + |
| 18 | +use std::sync::LazyLock; |
| 19 | + |
| 20 | +use libm_test::gen::CachedInput; |
| 21 | +use libm_test::{CheckOutput, GenerateInput, TupleCall}; |
| 22 | +use musl_math_sys as musl; |
| 23 | +use rand::{Rng, SeedableRng}; |
| 24 | +use rand_chacha::ChaCha8Rng; |
| 25 | + |
| 26 | +const SEED: [u8; 32] = *b"3.141592653589793238462643383279"; |
| 27 | + |
| 28 | +const NTESTS: usize = { |
| 29 | + let mut ntests = if cfg!(optimizations_enabled) { |
| 30 | + 5000 |
| 31 | + } else { |
| 32 | + 500 |
| 33 | + }; |
| 34 | + |
| 35 | + // Tests can be pretty slow on non-64-bit targets and, for some reason, ppc. |
| 36 | + if !cfg!(target_pointer_width = "64") || cfg!(target_arch = "powerpc64") { |
| 37 | + ntests /= 5; |
| 38 | + } |
| 39 | + |
| 40 | + ntests |
| 41 | +}; |
| 42 | + |
| 43 | +/// ULP allowed to differ from musl (note that musl itself may not be accurate). |
| 44 | +const ALLOWED_ULP: u32 = 2; |
| 45 | + |
| 46 | +/// Certain functions have different allowed ULP (consider these xfail). |
| 47 | +/// |
| 48 | +/// Currently this includes: |
| 49 | +/// - gamma functions that have higher errors |
| 50 | +/// - 32-bit functions fall back to a less precise algorithm. |
| 51 | +const ULP_OVERRIDES: &[(&str, u32)] = &[ |
| 52 | + #[cfg(x86_no_sse)] |
| 53 | + ("asinhf", 6), |
| 54 | + ("lgamma", 6), |
| 55 | + ("lgamma_r", 6), |
| 56 | + ("lgammaf", 6), |
| 57 | + ("lgammaf_r", 6), |
| 58 | + ("tanh", 4), |
| 59 | + ("tgamma", 8), |
| 60 | + #[cfg(not(target_pointer_width = "64"))] |
| 61 | + ("exp10", 4), |
| 62 | + #[cfg(not(target_pointer_width = "64"))] |
| 63 | + ("exp10f", 4), |
| 64 | +]; |
| 65 | + |
| 66 | +/// Tested inputs. |
| 67 | +static TEST_CASES: LazyLock<CachedInput> = LazyLock::new(|| make_test_cases(NTESTS)); |
| 68 | + |
| 69 | +/// The first argument to `jn` and `jnf` is the number of iterations. Make this a reasonable |
| 70 | +/// value so tests don't run forever. |
| 71 | +static TEST_CASES_JN: LazyLock<CachedInput> = LazyLock::new(|| { |
| 72 | + // It is easy to overflow the stack with these in debug mode |
| 73 | + let iterations = if cfg!(optimizations_enabled) && cfg!(target_pointer_width = "64") { |
| 74 | + 0xffff |
| 75 | + } else if cfg!(windows) { |
| 76 | + 0x00ff |
| 77 | + } else { |
| 78 | + 0x0fff |
| 79 | + }; |
| 80 | + |
| 81 | + let mut cases = (&*TEST_CASES).clone(); |
| 82 | + for case in cases.inputs_i32.iter_mut() { |
| 83 | + case.0 = iterations; |
| 84 | + } |
| 85 | + for case in cases.inputs_i32.iter_mut() { |
| 86 | + case.0 = iterations; |
| 87 | + } |
| 88 | + cases |
| 89 | +}); |
| 90 | + |
| 91 | +fn make_test_cases(ntests: usize) -> CachedInput { |
| 92 | + let mut rng = ChaCha8Rng::from_seed(SEED); |
| 93 | + |
| 94 | + let inputs_i32 = (0..ntests).map(|_| rng.gen::<(i32, i32, i32)>()).collect(); |
| 95 | + let inputs_f32 = (0..ntests).map(|_| rng.gen::<(f32, f32, f32)>()).collect(); |
| 96 | + let inputs_f64 = (0..ntests).map(|_| rng.gen::<(f64, f64, f64)>()).collect(); |
| 97 | + |
| 98 | + CachedInput { |
| 99 | + inputs_f32, |
| 100 | + inputs_f64, |
| 101 | + inputs_i32, |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +macro_rules! musl_rand_tests { |
| 106 | + ( |
| 107 | + fn_name: $fn_name:ident, |
| 108 | + CFn: $CFn:ty, |
| 109 | + CArgs: $CArgs:ty, |
| 110 | + CRet: $CRet:ty, |
| 111 | + RustFn: $RustFn:ty, |
| 112 | + RustArgs: $RustArgs:ty, |
| 113 | + RustRet: $RustRet:ty, |
| 114 | + attrs: [$($meta:meta)*] |
| 115 | + ) => { paste::paste! { |
| 116 | + #[test] |
| 117 | + $(#[$meta])* |
| 118 | + fn [< musl_random_ $fn_name >]() { |
| 119 | + let fname = stringify!($fn_name); |
| 120 | + let inputs = if fname == "jn" || fname == "jnf" { |
| 121 | + &TEST_CASES_JN |
| 122 | + } else { |
| 123 | + &TEST_CASES |
| 124 | + }; |
| 125 | + |
| 126 | + let ulp = match ULP_OVERRIDES.iter().find(|(name, _val)| name == &fname) { |
| 127 | + Some((_name, val)) => *val, |
| 128 | + None => ALLOWED_ULP, |
| 129 | + }; |
| 130 | + |
| 131 | + let cases = <CachedInput as GenerateInput<$RustArgs>>::get_cases(inputs); |
| 132 | + for input in cases { |
| 133 | + let mres = input.call(musl::$fn_name as $CFn); |
| 134 | + let cres = input.call(libm::$fn_name as $RustFn); |
| 135 | + |
| 136 | + mres.validate(cres, input, ulp); |
| 137 | + } |
| 138 | + } |
| 139 | + } }; |
| 140 | +} |
| 141 | + |
| 142 | +libm_macros::for_each_function! { |
| 143 | + callback: musl_rand_tests, |
| 144 | + skip: [], |
| 145 | + attributes: [ |
| 146 | + #[cfg_attr(x86_no_sse, ignore)] // FIXME(correctness): wrong result on i586 |
| 147 | + [exp10, exp10f, exp2, exp2f] |
| 148 | + ], |
| 149 | +} |
0 commit comments