|
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; |
| 1 | +use std::ops::RangeInclusive; |
5 | 2 |
|
| 3 | +use libm::support::Float; |
| 4 | +use rand::distributions::Standard; |
| 5 | +use rand::prelude::Distribution; |
6 | 6 | use rand::{Rng, SeedableRng}; |
7 | 7 | use rand_chacha::ChaCha8Rng; |
8 | 8 |
|
9 | | -use super::CachedInput; |
10 | | -use crate::{BaseName, CheckCtx, GenerateInput}; |
| 9 | +use super::KnownSize; |
| 10 | +use crate::run_cfg::{int_range, iteration_count}; |
| 11 | +use crate::{CheckCtx, GeneratorKind}; |
11 | 12 |
|
12 | 13 | const SEED: [u8; 32] = *b"3.141592653589793238462643383279"; |
13 | 14 |
|
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 | | - }; |
| 15 | +/// Generate a sequence of random values of this type. |
| 16 | +pub trait RandomInput { |
| 17 | + fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self>; |
| 18 | +} |
57 | 19 |
|
| 20 | +/// Generate a sequence of deterministically random floats. |
| 21 | +fn random_floats<F: Float>(count: u64) -> impl Iterator<Item = F> |
| 22 | +where |
| 23 | + Standard: Distribution<F::Int>, |
| 24 | +{ |
58 | 25 | let mut rng = ChaCha8Rng::from_seed(SEED); |
59 | 26 |
|
60 | | - for case in cases.inputs_i32.iter_mut() { |
61 | | - case.0 = rng.gen_range(3..=max_iterations); |
62 | | - } |
63 | | - |
64 | | - cases |
65 | | -}); |
| 27 | + // Generate integers to get a full range of bitpatterns (including NaNs), then convert back |
| 28 | + // to the float type. |
| 29 | + (0..count).map(move |_| F::from_bits(rng.gen::<F::Int>())) |
| 30 | +} |
66 | 31 |
|
67 | | -fn make_test_cases(ntests: usize) -> CachedInput { |
| 32 | +/// Generate a sequence of deterministically random `i32`s within a specified range. |
| 33 | +fn random_ints(count: u64, range: RangeInclusive<i32>) -> impl Iterator<Item = i32> { |
68 | 34 | let mut rng = ChaCha8Rng::from_seed(SEED); |
| 35 | + (0..count).map(move |_| rng.gen_range::<i32, _>(range.clone())) |
| 36 | +} |
| 37 | + |
| 38 | +macro_rules! impl_random_input { |
| 39 | + ($fty:ty) => { |
| 40 | + impl RandomInput for ($fty,) { |
| 41 | + fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> { |
| 42 | + let count = iteration_count(ctx, GeneratorKind::Random, 0); |
| 43 | + let iter = random_floats(count).map(|f: $fty| (f,)); |
| 44 | + KnownSize::new(iter, count) |
| 45 | + } |
| 46 | + } |
69 | 47 |
|
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 | | - ]; |
| 48 | + impl RandomInput for ($fty, $fty) { |
| 49 | + fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> { |
| 50 | + let count0 = iteration_count(ctx, GeneratorKind::Random, 0); |
| 51 | + let count1 = iteration_count(ctx, GeneratorKind::Random, 1); |
| 52 | + let iter = random_floats(count0) |
| 53 | + .flat_map(move |f1: $fty| random_floats(count1).map(move |f2: $fty| (f1, f2))); |
| 54 | + KnownSize::new(iter, count0 * count1) |
| 55 | + } |
| 56 | + } |
92 | 57 |
|
93 | | - inputs_i32.extend((0..(ntests - inputs_i32.len())).map(|_| rng.gen::<(i32, i32, i32)>())); |
| 58 | + impl RandomInput for ($fty, $fty, $fty) { |
| 59 | + fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> { |
| 60 | + let count0 = iteration_count(ctx, GeneratorKind::Random, 0); |
| 61 | + let count1 = iteration_count(ctx, GeneratorKind::Random, 1); |
| 62 | + let count2 = iteration_count(ctx, GeneratorKind::Random, 2); |
| 63 | + let iter = random_floats(count0).flat_map(move |f1: $fty| { |
| 64 | + random_floats(count1).flat_map(move |f2: $fty| { |
| 65 | + random_floats(count2).map(move |f3: $fty| (f1, f2, f3)) |
| 66 | + }) |
| 67 | + }); |
| 68 | + KnownSize::new(iter, count0 * count1 * count2) |
| 69 | + } |
| 70 | + } |
94 | 71 |
|
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 | | - })); |
| 72 | + impl RandomInput for (i32, $fty) { |
| 73 | + fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> { |
| 74 | + let count0 = iteration_count(ctx, GeneratorKind::Random, 0); |
| 75 | + let count1 = iteration_count(ctx, GeneratorKind::Random, 1); |
| 76 | + let range0 = int_range(ctx, 0); |
| 77 | + let iter = random_ints(count0, range0) |
| 78 | + .flat_map(move |f1: i32| random_floats(count1).map(move |f2: $fty| (f1, f2))); |
| 79 | + KnownSize::new(iter, count0 * count1) |
| 80 | + } |
| 81 | + } |
105 | 82 |
|
106 | | - CachedInput { inputs_f32, inputs_f64, inputs_i32 } |
| 83 | + impl RandomInput for ($fty, i32) { |
| 84 | + fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> { |
| 85 | + let count0 = iteration_count(ctx, GeneratorKind::Random, 0); |
| 86 | + let count1 = iteration_count(ctx, GeneratorKind::Random, 1); |
| 87 | + let range1 = int_range(ctx, 1); |
| 88 | + let iter = random_floats(count0).flat_map(move |f1: $fty| { |
| 89 | + random_ints(count1, range1.clone()).map(move |f2: i32| (f1, f2)) |
| 90 | + }); |
| 91 | + KnownSize::new(iter, count0 * count1) |
| 92 | + } |
| 93 | + } |
| 94 | + }; |
107 | 95 | } |
108 | 96 |
|
| 97 | +impl_random_input!(f32); |
| 98 | +impl_random_input!(f64); |
| 99 | + |
109 | 100 | /// Create a test case iterator. |
110 | | -pub fn get_test_cases<RustArgs>(ctx: &CheckCtx) -> impl Iterator<Item = RustArgs> |
111 | | -where |
112 | | - CachedInput: GenerateInput<RustArgs>, |
113 | | -{ |
114 | | - let inputs = if ctx.base_name == BaseName::Jn || ctx.base_name == BaseName::Yn { |
115 | | - &TEST_CASES_JN |
116 | | - } else { |
117 | | - &TEST_CASES |
118 | | - }; |
119 | | - inputs.get_cases() |
| 101 | +pub fn get_test_cases<RustArgs: RandomInput>( |
| 102 | + ctx: &CheckCtx, |
| 103 | +) -> impl Iterator<Item = RustArgs> + use<'_, RustArgs> { |
| 104 | + RustArgs::get_cases(ctx) |
120 | 105 | } |
0 commit comments