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

Commit bc0e731

Browse files
committed
Add a deterministic random generator
Create a test generator that creates a known number of random inputs and caches them, such that the same inputs are used for all functions.
1 parent ad1ad9f commit bc0e731

File tree

3 files changed

+117
-0
lines changed

3 files changed

+117
-0
lines changed

crates/libm-test/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ test-musl-serialized = ["rand"]
1515
anyhow = "1.0.90"
1616
libm = { path = "../.." }
1717
libm-macros = { path = "../libm-macros" }
18+
rand = "0.8.5"
19+
rand_chacha = "0.3.1"
20+
21+
[target.'cfg(target_family = "wasm")'.dependencies]
22+
# Enable randomness on WASM
23+
getrandom = { version = "0.2", features = ["js"] }
1824

1925
[build-dependencies]
2026
rand = { version = "0.8.5", optional = true }

crates/libm-test/src/gen.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Different generators that can create random or systematic bit patterns.
22
33
use crate::GenerateInput;
4+
pub mod random;
45

56
/// Helper type to turn any reusable input into a generator.
67
#[derive(Clone, Debug, Default)]

crates/libm-test/src/gen/random.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//! A simple generator that produces deterministic random input, caching to use the same
2+
//! inputs for all functions.
3+
4+
use std::sync::LazyLock;
5+
6+
use rand::{Rng, SeedableRng};
7+
use rand_chacha::ChaCha8Rng;
8+
9+
use super::CachedInput;
10+
use crate::GenerateInput;
11+
12+
const SEED: [u8; 32] = *b"3.141592653589793238462643383279";
13+
14+
/// Number of tests to run.
15+
const NTESTS: usize = {
16+
let mut ntests = if cfg!(optimizations_enabled) { 8000 } else { 800 };
17+
18+
// Tests seem to be pretty slow on non-64-bit targets, emulated ppc, and x86 MacOS
19+
if !cfg!(target_pointer_width = "64")
20+
|| cfg!(target_arch = "powerpc64")
21+
|| cfg!(all(target_arch = "x86_64", target_vendor = "apple"))
22+
{
23+
ntests /= 10;
24+
}
25+
26+
ntests
27+
};
28+
29+
/// Tested inputs.
30+
static TEST_CASES: LazyLock<CachedInput> = LazyLock::new(|| make_test_cases(NTESTS));
31+
32+
/// The first argument to `jn` and `jnf` is the number of iterations. Make this a reasonable
33+
/// value so tests don't run forever.
34+
static TEST_CASES_JN: LazyLock<CachedInput> = LazyLock::new(|| {
35+
// It is easy to overflow the stack with these in debug mode
36+
let max_iterations = if cfg!(optimizations_enabled) && cfg!(target_pointer_width = "64") {
37+
0xffff
38+
} else if cfg!(windows) {
39+
0x00ff
40+
} else {
41+
0x0fff
42+
};
43+
44+
let mut rng = ChaCha8Rng::from_seed(SEED);
45+
let mut cases = (&*TEST_CASES).clone();
46+
47+
// These functions are slow, limit them
48+
let len = cases.inputs_f32.len();
49+
cases.inputs_i32.truncate(len / 10);
50+
cases.inputs_f32.truncate(len / 10);
51+
cases.inputs_f64.truncate(len / 10);
52+
53+
for case in cases.inputs_i32.iter_mut() {
54+
case.0 = rng.gen_range(3..=max_iterations);
55+
}
56+
57+
cases
58+
});
59+
60+
fn make_test_cases(ntests: usize) -> CachedInput {
61+
let mut rng = ChaCha8Rng::from_seed(SEED);
62+
63+
// make sure we include some basic cases
64+
let mut inputs_i32 = vec![(0, 0, 0), (1, 1, 1), (-1, -1, -1)];
65+
let mut inputs_f32 = vec![
66+
(0.0, 0.0, 0.0),
67+
(f32::EPSILON, f32::EPSILON, f32::EPSILON),
68+
(f32::INFINITY, f32::INFINITY, f32::INFINITY),
69+
(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY),
70+
(f32::MAX, f32::MAX, f32::MAX),
71+
(f32::MIN, f32::MIN, f32::MIN),
72+
(f32::MIN_POSITIVE, f32::MIN_POSITIVE, f32::MIN_POSITIVE),
73+
(f32::NAN, f32::NAN, f32::NAN),
74+
];
75+
let mut inputs_f64 = vec![
76+
(0.0, 0.0, 0.0),
77+
(f64::EPSILON, f64::EPSILON, f64::EPSILON),
78+
(f64::INFINITY, f64::INFINITY, f64::INFINITY),
79+
(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY),
80+
(f64::MAX, f64::MAX, f64::MAX),
81+
(f64::MIN, f64::MIN, f64::MIN),
82+
(f64::MIN_POSITIVE, f64::MIN_POSITIVE, f64::MIN_POSITIVE),
83+
(f64::NAN, f64::NAN, f64::NAN),
84+
];
85+
86+
inputs_i32.extend((0..(ntests - inputs_i32.len())).map(|_| rng.gen::<(i32, i32, i32)>()));
87+
88+
// Generate integers to get a full range of bitpatterns, then convert back to
89+
// floats.
90+
inputs_f32.extend((0..(ntests - inputs_f32.len())).map(|_| {
91+
let ints = rng.gen::<(u32, u32, u32)>();
92+
(f32::from_bits(ints.0), f32::from_bits(ints.1), f32::from_bits(ints.2))
93+
}));
94+
inputs_f64.extend((0..(ntests - inputs_f64.len())).map(|_| {
95+
let ints = rng.gen::<(u64, u64, u64)>();
96+
(f64::from_bits(ints.0), f64::from_bits(ints.1), f64::from_bits(ints.2))
97+
}));
98+
99+
CachedInput { inputs_f32, inputs_f64, inputs_i32 }
100+
}
101+
102+
/// Create a test case iterator.
103+
pub fn get_test_cases<RustArgs>(fname: &str) -> impl Iterator<Item = RustArgs>
104+
where
105+
CachedInput: GenerateInput<RustArgs>,
106+
{
107+
let inputs = if fname == "jn" || fname == "jnf" { &TEST_CASES_JN } else { &TEST_CASES };
108+
109+
CachedInput::get_cases(inputs)
110+
}

0 commit comments

Comments
 (0)