| use std::env; |
| use std::ops::RangeInclusive; |
| use std::sync::LazyLock; |
| |
| use libm::support::{Float, Int}; |
| use rand::distr::uniform::SampleUniform; |
| use rand::distr::{Alphanumeric, StandardUniform}; |
| use rand::prelude::Distribution; |
| use rand::{RngExt, SeedableRng}; |
| use rand_chacha::ChaCha8Rng; |
| |
| use crate::CheckCtx; |
| use crate::generate::{KnownSize, product2, product3}; |
| use crate::num::full_range; |
| use crate::run_cfg::{int_range, iteration_count}; |
| |
| pub(crate) const SEED_ENV: &str = "LIBM_SEED"; |
| |
| pub static SEED: LazyLock<[u8; 32]> = LazyLock::new(|| { |
| let s = env::var(SEED_ENV).unwrap_or_else(|_| { |
| let mut rng = rand::rng(); |
| (0..32).map(|_| rng.sample(Alphanumeric) as char).collect() |
| }); |
| |
| s.as_bytes().try_into().unwrap_or_else(|_| { |
| panic!("Seed must be 32 characters, got `{s}`"); |
| }) |
| }); |
| |
| /// Generate a sequence of random values of this type. |
| pub trait RandomInput: Sized { |
| fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self> + Send, u64); |
| } |
| |
| /// Generate a sequence of deterministically random floats. |
| fn random_floats<F: Float>(count: u64) -> impl Iterator<Item = F> + Clone |
| where |
| StandardUniform: Distribution<F::Int>, |
| { |
| let mut rng = ChaCha8Rng::from_seed(*SEED); |
| |
| // Generate integers to get a full range of bitpatterns (including NaNs), then convert back |
| // to the float type. |
| (0..count).map(move |_| F::from_bits(rng.random::<F::Int>())) |
| } |
| |
| /// Generate a sequence of deterministically random `i32`s within a specified range. |
| fn random_ints<I>(count: u64, range: RangeInclusive<I>) -> impl Iterator<Item = I> + Clone |
| where |
| I: Int + SampleUniform, |
| { |
| let mut rng = ChaCha8Rng::from_seed(*SEED); |
| (0..count).map(move |_| rng.random_range::<I, _>(range.clone())) |
| } |
| |
| macro_rules! impl_random_input { |
| ($fty:ty) => { |
| impl RandomInput for ($fty,) { |
| fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) { |
| let count = iteration_count(ctx, 0); |
| let iter = random_floats(count).map(|f: $fty| (f,)); |
| (iter, count) |
| } |
| } |
| |
| impl RandomInput for ($fty, $fty) { |
| fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) { |
| let count0 = iteration_count(ctx, 0); |
| let count1 = iteration_count(ctx, 1); |
| let iter0 = random_floats(count0); |
| let iter1 = random_floats(count1); |
| let iter = product2(iter0, iter1); |
| let count = count0.strict_mul(count1); |
| (iter, count) |
| } |
| } |
| |
| impl RandomInput for ($fty, $fty, $fty) { |
| fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) { |
| let count0 = iteration_count(ctx, 0); |
| let count1 = iteration_count(ctx, 1); |
| let count2 = iteration_count(ctx, 2); |
| let iter0 = random_floats(count0); |
| let iter1 = random_floats(count1); |
| let iter2 = random_floats(count2); |
| let iter = product3(iter0, iter1, iter2); |
| let count = count0.strict_mul(count1).strict_mul(count2); |
| (iter, count) |
| } |
| } |
| |
| impl RandomInput for (i32, $fty) { |
| fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) { |
| let count0 = iteration_count(ctx, 0); |
| let count1 = iteration_count(ctx, 1); |
| let range0 = int_range::<i32>(ctx, 0).unwrap_or(full_range()); |
| let iter0 = random_ints(count0, range0); |
| let iter1 = random_floats(count1); |
| let iter = product2(iter0, iter1); |
| let count = count0.strict_mul(count1); |
| (iter, count) |
| } |
| } |
| |
| impl RandomInput for ($fty, i32) { |
| fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) { |
| let count0 = iteration_count(ctx, 0); |
| let count1 = iteration_count(ctx, 1); |
| let range1 = int_range::<i32>(ctx, 1).unwrap_or(full_range()); |
| let iter0 = random_floats(count0); |
| let iter1 = random_ints(count1, range1.clone()); |
| let iter = product2(iter0, iter1); |
| let count = count0.strict_mul(count1); |
| (iter, count) |
| } |
| } |
| }; |
| } |
| |
| #[cfg(f16_enabled)] |
| impl_random_input!(f16); |
| impl_random_input!(f32); |
| impl_random_input!(f64); |
| #[cfg(f128_enabled)] |
| impl_random_input!(f128); |
| |
| macro_rules! impl_random_input_int { |
| (@skip_u32 $ity:ty) => { |
| impl RandomInput for ($ity,) { |
| fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) { |
| let count = iteration_count(ctx, 0); |
| let range = int_range::<$ity>(ctx, 0).unwrap_or(full_range()); |
| let iter = random_ints(count, range).map(|f: $ity| (f,)); |
| (iter, count) |
| } |
| } |
| |
| impl RandomInput for ($ity, $ity) { |
| fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) { |
| let count0 = iteration_count(ctx, 0); |
| let count1 = iteration_count(ctx, 1); |
| let range0 = int_range::<$ity>(ctx, 0).unwrap_or(full_range()); |
| let range1 = int_range::<$ity>(ctx, 1).unwrap_or(full_range()); |
| let iter0 = random_ints(count0, range0); |
| let iter1 = random_ints(count1, range1.clone()); |
| let iter = product2(iter0, iter1); |
| (iter, count0 * count1) |
| } |
| } |
| }; |
| ($ity:ty) => { |
| impl_random_input_int!(@skip_u32 $ity); |
| |
| impl RandomInput for ($ity, u32) { |
| fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) { |
| let count0 = iteration_count(ctx, 0); |
| let count1 = iteration_count(ctx, 1); |
| let range0 = int_range::<$ity>(ctx, 0).unwrap_or(full_range()); |
| let range1 = int_range::<u32>(ctx, 1).unwrap_or(full_range()); |
| let iter0 = random_ints(count0, range0); |
| let iter1 = random_ints(count1, range1.clone()); |
| let iter = product2(iter0, iter1); |
| (iter, count0 * count1) |
| } |
| } |
| }; |
| } |
| |
| impl_random_input_int!(i32); |
| impl_random_input_int!(i64); |
| impl_random_input_int!(i128); |
| impl_random_input_int!(@skip_u32 u32); |
| impl_random_input_int!(u64); |
| impl_random_input_int!(u128); |
| |
| /// Create a test case iterator. |
| pub fn get_test_cases<RustArgs: RandomInput>( |
| ctx: &CheckCtx, |
| ) -> ( |
| impl Iterator<Item = RustArgs> + Send + use<'_, RustArgs>, |
| u64, |
| ) { |
| let (iter, count) = RustArgs::get_cases(ctx); |
| |
| // Wrap in `KnownSize` so we get an assertion if the cuunt is wrong. |
| (KnownSize::new(iter, count), count) |
| } |