|
1 |
| -//! A simple generator that produces deterministic random input, caching to use the same |
2 |
| -//! inputs for all functions. |
3 |
| -
|
| 1 | +use std::env; |
| 2 | +use std::ops::RangeInclusive; |
4 | 3 | use std::sync::LazyLock;
|
5 | 4 |
|
| 5 | +use libm::support::Float; |
| 6 | +use rand::distributions::{Alphanumeric, Standard}; |
| 7 | +use rand::prelude::Distribution; |
6 | 8 | use rand::{Rng, SeedableRng};
|
7 | 9 | use rand_chacha::ChaCha8Rng;
|
8 | 10 |
|
9 |
| -use super::CachedInput; |
10 |
| -use crate::{BaseName, CheckCtx, GenerateInput}; |
11 |
| - |
12 |
| -const SEED: [u8; 32] = *b"3.141592653589793238462643383279"; |
13 |
| - |
14 |
| -/// Number of tests to run. |
15 |
| -// FIXME(ntests): clean this up when possible |
16 |
| -const NTESTS: usize = { |
17 |
| - if cfg!(optimizations_enabled) { |
18 |
| - if crate::emulated() |
19 |
| - || !cfg!(target_pointer_width = "64") |
20 |
| - || cfg!(all(target_arch = "x86_64", target_vendor = "apple")) |
21 |
| - { |
22 |
| - // Tests are pretty slow on non-64-bit targets, x86 MacOS, and targets that run |
23 |
| - // in QEMU. |
24 |
| - 100_000 |
25 |
| - } else { |
26 |
| - 5_000_000 |
27 |
| - } |
28 |
| - } else { |
29 |
| - // Without optimizations just run a quick check |
30 |
| - 800 |
31 |
| - } |
32 |
| -}; |
33 |
| - |
34 |
| -/// Tested inputs. |
35 |
| -static TEST_CASES: LazyLock<CachedInput> = LazyLock::new(|| make_test_cases(NTESTS)); |
36 |
| - |
37 |
| -/// The first argument to `jn` and `jnf` is the number of iterations. Make this a reasonable |
38 |
| -/// value so tests don't run forever. |
39 |
| -static TEST_CASES_JN: LazyLock<CachedInput> = LazyLock::new(|| { |
40 |
| - // Start with regular test cases |
41 |
| - let mut cases = (*TEST_CASES).clone(); |
42 |
| - |
43 |
| - // These functions are extremely slow, limit them |
44 |
| - let ntests_jn = (NTESTS / 1000).max(80); |
45 |
| - cases.inputs_i32.truncate(ntests_jn); |
46 |
| - cases.inputs_f32.truncate(ntests_jn); |
47 |
| - cases.inputs_f64.truncate(ntests_jn); |
48 |
| - |
49 |
| - // It is easy to overflow the stack with these in debug mode |
50 |
| - let max_iterations = if cfg!(optimizations_enabled) && cfg!(target_pointer_width = "64") { |
51 |
| - 0xffff |
52 |
| - } else if cfg!(windows) { |
53 |
| - 0x00ff |
54 |
| - } else { |
55 |
| - 0x0fff |
56 |
| - }; |
| 11 | +use super::KnownSize; |
| 12 | +use crate::run_cfg::{int_range, iteration_count}; |
| 13 | +use crate::{CheckCtx, GeneratorKind}; |
57 | 14 |
|
58 |
| - let mut rng = ChaCha8Rng::from_seed(SEED); |
| 15 | +pub(crate) const SEED_ENV: &str = "LIBM_SEED"; |
59 | 16 |
|
60 |
| - for case in cases.inputs_i32.iter_mut() { |
61 |
| - case.0 = rng.gen_range(3..=max_iterations); |
62 |
| - } |
| 17 | +pub(crate) static SEED: LazyLock<[u8; 32]> = LazyLock::new(|| { |
| 18 | + let s = env::var(SEED_ENV).unwrap_or_else(|_| { |
| 19 | + let mut rng = rand::thread_rng(); |
| 20 | + (0..32).map(|_| rng.sample(Alphanumeric) as char).collect() |
| 21 | + }); |
63 | 22 |
|
64 |
| - cases |
| 23 | + s.as_bytes().try_into().unwrap_or_else(|_| { |
| 24 | + panic!("Seed must be 32 characters, got `{s}`"); |
| 25 | + }) |
65 | 26 | });
|
66 | 27 |
|
67 |
| -fn make_test_cases(ntests: usize) -> CachedInput { |
68 |
| - let mut rng = ChaCha8Rng::from_seed(SEED); |
69 |
| - |
70 |
| - // make sure we include some basic cases |
71 |
| - let mut inputs_i32 = vec![(0, 0, 0), (1, 1, 1), (-1, -1, -1)]; |
72 |
| - let mut inputs_f32 = vec![ |
73 |
| - (0.0, 0.0, 0.0), |
74 |
| - (f32::EPSILON, f32::EPSILON, f32::EPSILON), |
75 |
| - (f32::INFINITY, f32::INFINITY, f32::INFINITY), |
76 |
| - (f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY), |
77 |
| - (f32::MAX, f32::MAX, f32::MAX), |
78 |
| - (f32::MIN, f32::MIN, f32::MIN), |
79 |
| - (f32::MIN_POSITIVE, f32::MIN_POSITIVE, f32::MIN_POSITIVE), |
80 |
| - (f32::NAN, f32::NAN, f32::NAN), |
81 |
| - ]; |
82 |
| - let mut inputs_f64 = vec![ |
83 |
| - (0.0, 0.0, 0.0), |
84 |
| - (f64::EPSILON, f64::EPSILON, f64::EPSILON), |
85 |
| - (f64::INFINITY, f64::INFINITY, f64::INFINITY), |
86 |
| - (f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY), |
87 |
| - (f64::MAX, f64::MAX, f64::MAX), |
88 |
| - (f64::MIN, f64::MIN, f64::MIN), |
89 |
| - (f64::MIN_POSITIVE, f64::MIN_POSITIVE, f64::MIN_POSITIVE), |
90 |
| - (f64::NAN, f64::NAN, f64::NAN), |
91 |
| - ]; |
92 |
| - |
93 |
| - inputs_i32.extend((0..(ntests - inputs_i32.len())).map(|_| rng.gen::<(i32, i32, i32)>())); |
94 |
| - |
95 |
| - // Generate integers to get a full range of bitpatterns, then convert back to |
96 |
| - // floats. |
97 |
| - inputs_f32.extend((0..(ntests - inputs_f32.len())).map(|_| { |
98 |
| - let ints = rng.gen::<(u32, u32, u32)>(); |
99 |
| - (f32::from_bits(ints.0), f32::from_bits(ints.1), f32::from_bits(ints.2)) |
100 |
| - })); |
101 |
| - inputs_f64.extend((0..(ntests - inputs_f64.len())).map(|_| { |
102 |
| - let ints = rng.gen::<(u64, u64, u64)>(); |
103 |
| - (f64::from_bits(ints.0), f64::from_bits(ints.1), f64::from_bits(ints.2)) |
104 |
| - })); |
105 |
| - |
106 |
| - CachedInput { inputs_f32, inputs_f64, inputs_i32 } |
| 28 | +/// Generate a sequence of random values of this type. |
| 29 | +pub trait RandomInput { |
| 30 | + fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self>; |
107 | 31 | }
|
108 | 32 |
|
109 |
| -/// Create a test case iterator. |
110 |
| -pub fn get_test_cases<RustArgs>(ctx: &CheckCtx) -> impl Iterator<Item = RustArgs> |
| 33 | +/// Generate a sequence of deterministically random floats. |
| 34 | +fn random_floats<F: Float>(count: u64) -> impl Iterator<Item = F> |
111 | 35 | where
|
112 |
| - CachedInput: GenerateInput<RustArgs>, |
| 36 | + Standard: Distribution<F::Int>, |
113 | 37 | {
|
114 |
| - let inputs = if ctx.base_name == BaseName::Jn || ctx.base_name == BaseName::Yn { |
115 |
| - &TEST_CASES_JN |
116 |
| - } else { |
117 |
| - &TEST_CASES |
| 38 | + let mut rng = ChaCha8Rng::from_seed(*SEED); |
| 39 | + |
| 40 | + // Generate integers to get a full range of bitpatterns (including NaNs), then convert back |
| 41 | + // to the float type. |
| 42 | + (0..count).map(move |_| F::from_bits(rng.gen::<F::Int>())) |
| 43 | +} |
| 44 | + |
| 45 | +/// Generate a sequence of deterministically random `i32`s within a specified range. |
| 46 | +fn random_ints(count: u64, range: RangeInclusive<i32>) -> impl Iterator<Item = i32> { |
| 47 | + let mut rng = ChaCha8Rng::from_seed(*SEED); |
| 48 | + (0..count).map(move |_| rng.gen_range::<i32, _>(range.clone())) |
| 49 | +} |
| 50 | + |
| 51 | +macro_rules! impl_random_input { |
| 52 | + ($fty:ty) => { |
| 53 | + impl RandomInput for ($fty,) { |
| 54 | + fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> { |
| 55 | + let count = iteration_count(ctx, GeneratorKind::Random, 0); |
| 56 | + let iter = random_floats(count).map(|f: $fty| (f,)); |
| 57 | + KnownSize::new(iter, count) |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + impl RandomInput for ($fty, $fty) { |
| 62 | + fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> { |
| 63 | + let count0 = iteration_count(ctx, GeneratorKind::Random, 0); |
| 64 | + let count1 = iteration_count(ctx, GeneratorKind::Random, 1); |
| 65 | + let iter = random_floats(count0) |
| 66 | + .flat_map(move |f1: $fty| random_floats(count1).map(move |f2: $fty| (f1, f2))); |
| 67 | + KnownSize::new(iter, count0 * count1) |
| 68 | + } |
| 69 | + } |
| 70 | + |
| 71 | + impl RandomInput for ($fty, $fty, $fty) { |
| 72 | + fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> { |
| 73 | + let count0 = iteration_count(ctx, GeneratorKind::Random, 0); |
| 74 | + let count1 = iteration_count(ctx, GeneratorKind::Random, 1); |
| 75 | + let count2 = iteration_count(ctx, GeneratorKind::Random, 2); |
| 76 | + let iter = random_floats(count0).flat_map(move |f1: $fty| { |
| 77 | + random_floats(count1).flat_map(move |f2: $fty| { |
| 78 | + random_floats(count2).map(move |f3: $fty| (f1, f2, f3)) |
| 79 | + }) |
| 80 | + }); |
| 81 | + KnownSize::new(iter, count0 * count1 * count2) |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + impl RandomInput for (i32, $fty) { |
| 86 | + fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> { |
| 87 | + let count0 = iteration_count(ctx, GeneratorKind::Random, 0); |
| 88 | + let count1 = iteration_count(ctx, GeneratorKind::Random, 1); |
| 89 | + let range0 = int_range(ctx, 0); |
| 90 | + let iter = random_ints(count0, range0) |
| 91 | + .flat_map(move |f1: i32| random_floats(count1).map(move |f2: $fty| (f1, f2))); |
| 92 | + KnownSize::new(iter, count0 * count1) |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + impl RandomInput for ($fty, i32) { |
| 97 | + fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> { |
| 98 | + let count0 = iteration_count(ctx, GeneratorKind::Random, 0); |
| 99 | + let count1 = iteration_count(ctx, GeneratorKind::Random, 1); |
| 100 | + let range1 = int_range(ctx, 1); |
| 101 | + let iter = random_floats(count0).flat_map(move |f1: $fty| { |
| 102 | + random_ints(count1, range1.clone()).map(move |f2: i32| (f1, f2)) |
| 103 | + }); |
| 104 | + KnownSize::new(iter, count0 * count1) |
| 105 | + } |
| 106 | + } |
118 | 107 | };
|
119 |
| - inputs.get_cases() |
| 108 | +} |
| 109 | + |
| 110 | +impl_random_input!(f32); |
| 111 | +impl_random_input!(f64); |
| 112 | + |
| 113 | +/// Create a test case iterator. |
| 114 | +pub fn get_test_cases<RustArgs: RandomInput>( |
| 115 | + ctx: &CheckCtx, |
| 116 | +) -> impl Iterator<Item = RustArgs> + use<'_, RustArgs> { |
| 117 | + RustArgs::get_cases(ctx) |
120 | 118 | }
|
0 commit comments