diff --git a/crates/libm-test/src/gen/domain_logspace.rs b/crates/libm-test/src/gen/domain_logspace.rs index 3d8a3e7fe..c6963ad43 100644 --- a/crates/libm-test/src/gen/domain_logspace.rs +++ b/crates/libm-test/src/gen/domain_logspace.rs @@ -27,5 +27,5 @@ where let start = domain.range_start(); let end = domain.range_end(); let steps = OpITy::::try_from(ntests).unwrap_or(OpITy::::MAX); - logspace(start, end, steps).map(|v| (v,)) + logspace(start, end, steps).0.map(|v| (v,)) } diff --git a/crates/libm-test/src/gen/extensive.rs b/crates/libm-test/src/gen/extensive.rs index d724226e9..fb709e546 100644 --- a/crates/libm-test/src/gen/extensive.rs +++ b/crates/libm-test/src/gen/extensive.rs @@ -1,19 +1,18 @@ use std::fmt; use std::ops::RangeInclusive; -use libm::support::MinInt; +use libm::support::{Float, MinInt}; use crate::domain::HasDomain; -use crate::gen::KnownSize; use crate::op::OpITy; use crate::run_cfg::{int_range, iteration_count}; -use crate::{CheckCtx, GeneratorKind, MathOp, logspace}; +use crate::{CheckCtx, GeneratorKind, MathOp, linear_ints, logspace}; /// Generate a sequence of inputs that either cover the domain in completeness (for smaller float /// types and single argument functions) or provide evenly spaced inputs across the domain with /// approximately `u32::MAX` total iterations. pub trait ExtensiveInput { - fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator + Send; + fn get_cases(ctx: &CheckCtx) -> (impl Iterator + Send, u64); } /// Construct an iterator from `logspace` and also calculate the total number of steps expected @@ -21,24 +20,60 @@ pub trait ExtensiveInput { fn logspace_steps( start: Op::FTy, end: Op::FTy, - ctx: &CheckCtx, - argnum: usize, + max_steps: u64, ) -> (impl Iterator + Clone, u64) where Op: MathOp, OpITy: TryFrom, + u64: TryFrom, Error: fmt::Debug>, RangeInclusive>: Iterator, { - let max_steps = iteration_count(ctx, GeneratorKind::Extensive, argnum); let max_steps = OpITy::::try_from(max_steps).unwrap_or(OpITy::::MAX); - let iter = logspace(start, end, max_steps); + let (iter, steps) = logspace(start, end, max_steps); + + // `steps` will be <= the original `max_steps`, which is a `u64`. + (iter, steps.try_into().unwrap()) +} + +/// Represents the iterator in either `Left` or `Right`. +enum EitherIter { + A(A), + B(B), +} - // `logspace` can't implement `ExactSizeIterator` because of the range, but its size hint - // should be accurate (assuming <= usize::MAX iterations). - let size_hint = iter.size_hint(); - assert_eq!(size_hint.0, size_hint.1.unwrap()); +impl, B: Iterator> Iterator for EitherIter { + type Item = T; - (iter, size_hint.0.try_into().unwrap()) + fn next(&mut self) -> Option { + match self { + Self::A(iter) => iter.next(), + Self::B(iter) => iter.next(), + } + } + + fn size_hint(&self) -> (usize, Option) { + match self { + Self::A(iter) => iter.size_hint(), + Self::B(iter) => iter.size_hint(), + } + } +} + +/// Gets the total number of possible values, returning `None` if that number doesn't fit in a +/// `u64`. +fn value_count() -> Option +where + u64: TryFrom, +{ + u64::try_from(F::Int::MAX).ok().and_then(|max| max.checked_add(1)) +} + +/// Returns an iterator of every possible value of type `F`. +fn all_values() -> impl Iterator +where + RangeInclusive: Iterator, +{ + (F::Int::MIN..=F::Int::MAX).map(|bits| F::from_bits(bits)) } macro_rules! impl_extensive_input { @@ -48,12 +83,23 @@ macro_rules! impl_extensive_input { Op: MathOp, Op: HasDomain, { - fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator { - let start = Op::DOMAIN.range_start(); - let end = Op::DOMAIN.range_end(); - let (iter0, steps0) = logspace_steps::(start, end, ctx, 0); - let iter0 = iter0.map(|v| (v,)); - KnownSize::new(iter0, steps0) + fn get_cases(ctx: &CheckCtx) -> (impl Iterator, u64) { + let max_steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0); + // `f16` and `f32` can have exhaustive tests. + match value_count::() { + Some(steps0) if steps0 <= max_steps0 => { + let iter0 = all_values(); + let iter0 = iter0.map(|v| (v,)); + (EitherIter::A(iter0), steps0) + } + _ => { + let start = Op::DOMAIN.range_start(); + let end = Op::DOMAIN.range_end(); + let (iter0, steps0) = logspace_steps::(start, end, max_steps0); + let iter0 = iter0.map(|v| (v,)); + (EitherIter::B(iter0), steps0) + } + } } } @@ -61,15 +107,28 @@ macro_rules! impl_extensive_input { where Op: MathOp, { - fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator { - let start = <$fty>::NEG_INFINITY; - let end = <$fty>::INFINITY; - let (iter0, steps0) = logspace_steps::(start, end, ctx, 0); - let (iter1, steps1) = logspace_steps::(start, end, ctx, 1); - let iter = - iter0.flat_map(move |first| iter1.clone().map(move |second| (first, second))); - let count = steps0.checked_mul(steps1).unwrap(); - KnownSize::new(iter, count) + fn get_cases(ctx: &CheckCtx) -> (impl Iterator, u64) { + let max_steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0); + let max_steps1 = iteration_count(ctx, GeneratorKind::Extensive, 1); + // `f16` can have exhaustive tests. + match value_count::() { + Some(count) if count <= max_steps0 && count <= max_steps1 => { + let iter = all_values() + .flat_map(|first| all_values().map(move |second| (first, second))); + (EitherIter::A(iter), count.checked_mul(count).unwrap()) + } + _ => { + let start = <$fty>::NEG_INFINITY; + let end = <$fty>::INFINITY; + let (iter0, steps0) = logspace_steps::(start, end, max_steps0); + let (iter1, steps1) = logspace_steps::(start, end, max_steps1); + let iter = iter0.flat_map(move |first| { + iter1.clone().map(move |second| (first, second)) + }); + let count = steps0.checked_mul(steps1).unwrap(); + (EitherIter::B(iter), count) + } + } } } @@ -77,22 +136,41 @@ macro_rules! impl_extensive_input { where Op: MathOp, { - fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator { - let start = <$fty>::NEG_INFINITY; - let end = <$fty>::INFINITY; - - let (iter0, steps0) = logspace_steps::(start, end, ctx, 0); - let (iter1, steps1) = logspace_steps::(start, end, ctx, 1); - let (iter2, steps2) = logspace_steps::(start, end, ctx, 2); - - let iter = iter0 - .flat_map(move |first| iter1.clone().map(move |second| (first, second))) - .flat_map(move |(first, second)| { - iter2.clone().map(move |third| (first, second, third)) - }); - let count = steps0.checked_mul(steps1).unwrap().checked_mul(steps2).unwrap(); - - KnownSize::new(iter, count) + fn get_cases(ctx: &CheckCtx) -> (impl Iterator, u64) { + let max_steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0); + let max_steps1 = iteration_count(ctx, GeneratorKind::Extensive, 1); + let max_steps2 = iteration_count(ctx, GeneratorKind::Extensive, 2); + // `f16` can be exhaustive tested if `LIBM_EXTENSIVE_TESTS` is incresed. + match value_count::() { + Some(count) + if count <= max_steps0 && count <= max_steps1 && count <= max_steps2 => + { + let iter = all_values().flat_map(|first| { + all_values().flat_map(move |second| { + all_values().map(move |third| (first, second, third)) + }) + }); + (EitherIter::A(iter), count.checked_pow(3).unwrap()) + } + _ => { + let start = <$fty>::NEG_INFINITY; + let end = <$fty>::INFINITY; + + let (iter0, steps0) = logspace_steps::(start, end, max_steps0); + let (iter1, steps1) = logspace_steps::(start, end, max_steps1); + let (iter2, steps2) = logspace_steps::(start, end, max_steps2); + + let iter = iter0 + .flat_map(move |first| iter1.clone().map(move |second| (first, second))) + .flat_map(move |(first, second)| { + iter2.clone().map(move |third| (first, second, third)) + }); + let count = + steps0.checked_mul(steps1).unwrap().checked_mul(steps2).unwrap(); + + (EitherIter::B(iter), count) + } + } } } @@ -100,19 +178,32 @@ macro_rules! impl_extensive_input { where Op: MathOp, { - fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator { - let start = <$fty>::NEG_INFINITY; - let end = <$fty>::INFINITY; + fn get_cases(ctx: &CheckCtx) -> (impl Iterator, u64) { + let range0 = int_range(ctx, GeneratorKind::Extensive, 0); + let max_steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0); + let max_steps1 = iteration_count(ctx, GeneratorKind::Extensive, 1); + match value_count::() { + Some(count1) if count1 <= max_steps1 => { + let (iter0, steps0) = linear_ints(range0, max_steps0); + let iter = iter0 + .flat_map(move |first| all_values().map(move |second| (first, second))); + (EitherIter::A(iter), steps0.checked_mul(count1).unwrap()) + } + _ => { + let start = <$fty>::NEG_INFINITY; + let end = <$fty>::INFINITY; - let iter0 = int_range(ctx, GeneratorKind::Extensive, 0); - let steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0); - let (iter1, steps1) = logspace_steps::(start, end, ctx, 1); + let (iter0, steps0) = linear_ints(range0, max_steps0); + let (iter1, steps1) = logspace_steps::(start, end, max_steps1); - let iter = - iter0.flat_map(move |first| iter1.clone().map(move |second| (first, second))); - let count = steps0.checked_mul(steps1).unwrap(); + let iter = iter0.flat_map(move |first| { + iter1.clone().map(move |second| (first, second)) + }); + let count = steps0.checked_mul(steps1).unwrap(); - KnownSize::new(iter, count) + (EitherIter::B(iter), count) + } + } } } @@ -120,19 +211,33 @@ macro_rules! impl_extensive_input { where Op: MathOp, { - fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator { - let start = <$fty>::NEG_INFINITY; - let end = <$fty>::INFINITY; + fn get_cases(ctx: &CheckCtx) -> (impl Iterator, u64) { + let max_steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0); + let range1 = int_range(ctx, GeneratorKind::Extensive, 1); + let max_steps1 = iteration_count(ctx, GeneratorKind::Extensive, 1); + match value_count::() { + Some(count0) if count0 <= max_steps0 => { + let (iter1, steps1) = linear_ints(range1, max_steps1); + let iter = all_values().flat_map(move |first| { + iter1.clone().map(move |second| (first, second)) + }); + (EitherIter::A(iter), count0.checked_mul(steps1).unwrap()) + } + _ => { + let start = <$fty>::NEG_INFINITY; + let end = <$fty>::INFINITY; - let (iter0, steps0) = logspace_steps::(start, end, ctx, 0); - let iter1 = int_range(ctx, GeneratorKind::Extensive, 0); - let steps1 = iteration_count(ctx, GeneratorKind::Extensive, 0); + let (iter0, steps0) = logspace_steps::(start, end, max_steps0); + let (iter1, steps1) = linear_ints(range1, max_steps1); - let iter = - iter0.flat_map(move |first| iter1.clone().map(move |second| (first, second))); - let count = steps0.checked_mul(steps1).unwrap(); + let iter = iter0.flat_map(move |first| { + iter1.clone().map(move |second| (first, second)) + }); + let count = steps0.checked_mul(steps1).unwrap(); - KnownSize::new(iter, count) + (EitherIter::B(iter), count) + } + } } } }; @@ -145,10 +250,10 @@ impl_extensive_input!(f64); #[cfg(f128_enabled)] impl_extensive_input!(f128); -/// Create a test case iterator for extensive inputs. +/// Create a test case iterator for extensive inputs. Also returns the total test case count. pub fn get_test_cases( ctx: &CheckCtx, -) -> impl ExactSizeIterator + Send + use<'_, Op> +) -> (impl Iterator + Send + use<'_, Op>, u64) where Op: MathOp, Op::RustArgs: ExtensiveInput, diff --git a/crates/libm-test/src/lib.rs b/crates/libm-test/src/lib.rs index 251114a0d..6e7017f09 100644 --- a/crates/libm-test/src/lib.rs +++ b/crates/libm-test/src/lib.rs @@ -22,7 +22,7 @@ use std::time::SystemTime; pub use f8_impl::f8; pub use libm::support::{Float, Int, IntTy, MinInt}; -pub use num::{FloatExt, logspace}; +pub use num::{FloatExt, linear_ints, logspace}; pub use op::{ BaseName, FloatTy, Identifier, MathOp, OpCFn, OpCRet, OpFTy, OpRustFn, OpRustRet, Ty, }; diff --git a/crates/libm-test/src/num.rs b/crates/libm-test/src/num.rs index f693ef02f..eed941423 100644 --- a/crates/libm-test/src/num.rs +++ b/crates/libm-test/src/num.rs @@ -215,7 +215,13 @@ fn as_ulp_steps(x: F) -> Option { /// to logarithmic spacing of their values. /// /// Note that this tends to skip negative zero, so that needs to be checked explicitly. -pub fn logspace(start: F, end: F, steps: F::Int) -> impl Iterator + Clone +/// +/// Returns `(iterator, iterator_length)`. +pub fn logspace( + start: F, + end: F, + steps: F::Int, +) -> (impl Iterator + Clone, F::Int) where RangeInclusive: Iterator, { @@ -223,17 +229,42 @@ where assert!(!end.is_nan()); assert!(end >= start); - let mut steps = steps.checked_sub(F::Int::ONE).expect("`steps` must be at least 2"); + let steps = steps.checked_sub(F::Int::ONE).expect("`steps` must be at least 2"); let between = ulp_between(start, end).expect("`start` or `end` is NaN"); let spacing = (between / steps).max(F::Int::ONE); - steps = steps.min(between); // At maximum, one step per ULP + let steps = steps.min(between); // At maximum, one step per ULP let mut x = start; - (F::Int::ZERO..=steps).map(move |_| { - let ret = x; - x = x.n_up(spacing); - ret - }) + ( + (F::Int::ZERO..=steps).map(move |_| { + let ret = x; + x = x.n_up(spacing); + ret + }), + steps + F::Int::ONE, + ) +} + +/// Returns an iterator of up to `steps` integers evenly distributed. +pub fn linear_ints( + range: RangeInclusive, + steps: u64, +) -> (impl Iterator + Clone, u64) { + let steps = steps.checked_sub(1).unwrap(); + let between = u64::from(range.start().abs_diff(*range.end())); + let spacing = i32::try_from((between / steps).max(1)).unwrap(); + let steps = steps.min(between); + let mut x: i32 = *range.start(); + ( + (0..=steps).map(move |_| { + let res = x; + // Wrapping add to avoid panic on last item (where `x` could overflow past i32::MAX as + // there is no next item). + x = x.wrapping_add(spacing); + res + }), + steps + 1, + ) } #[cfg(test)] @@ -422,19 +453,55 @@ mod tests { #[test] fn test_logspace() { - let ls: Vec<_> = logspace(f8::from_bits(0x0), f8::from_bits(0x4), 2).collect(); + let (ls, count) = logspace(f8::from_bits(0x0), f8::from_bits(0x4), 2); + let ls: Vec<_> = ls.collect(); let exp = [f8::from_bits(0x0), f8::from_bits(0x4)]; assert_eq!(ls, exp); + assert_eq!(ls.len(), usize::from(count)); - let ls: Vec<_> = logspace(f8::from_bits(0x0), f8::from_bits(0x4), 3).collect(); + let (ls, count) = logspace(f8::from_bits(0x0), f8::from_bits(0x4), 3); + let ls: Vec<_> = ls.collect(); let exp = [f8::from_bits(0x0), f8::from_bits(0x2), f8::from_bits(0x4)]; assert_eq!(ls, exp); + assert_eq!(ls.len(), usize::from(count)); // Check that we include all values with no repeats if `steps` exceeds the maximum number // of steps. - let ls: Vec<_> = logspace(f8::from_bits(0x0), f8::from_bits(0x3), 10).collect(); + let (ls, count) = logspace(f8::from_bits(0x0), f8::from_bits(0x3), 10); + let ls: Vec<_> = ls.collect(); let exp = [f8::from_bits(0x0), f8::from_bits(0x1), f8::from_bits(0x2), f8::from_bits(0x3)]; assert_eq!(ls, exp); + assert_eq!(ls.len(), usize::from(count)); + } + + #[test] + fn test_linear_ints() { + let (ints, count) = linear_ints(0..=4, 2); + let ints: Vec<_> = ints.collect(); + let exp = [0, 4]; + assert_eq!(ints, exp); + assert_eq!(ints.len(), usize::try_from(count).unwrap()); + + let (ints, count) = linear_ints(0..=4, 3); + let ints: Vec<_> = ints.collect(); + let exp = [0, 2, 4]; + assert_eq!(ints, exp); + assert_eq!(ints.len(), usize::try_from(count).unwrap()); + + // Check that we include all values with no repeats if `steps` exceeds the maximum number + // of steps. + let (ints, count) = linear_ints(0x0..=0x3, 10); + let ints: Vec<_> = ints.collect(); + let exp = [0, 1, 2, 3]; + assert_eq!(ints, exp); + assert_eq!(ints.len(), usize::try_from(count).unwrap()); + + // Check that there are no panics around `i32::MAX`. + let (ints, count) = linear_ints(i32::MAX - 1..=i32::MAX, 5); + let ints: Vec<_> = ints.collect(); + let exp = [i32::MAX - 1, i32::MAX]; + assert_eq!(ints, exp); + assert_eq!(ints.len(), usize::try_from(count).unwrap()); } #[test] diff --git a/crates/libm-test/tests/z_extensive/run.rs b/crates/libm-test/tests/z_extensive/run.rs index 7acff5324..07f4d5370 100644 --- a/crates/libm-test/tests/z_extensive/run.rs +++ b/crates/libm-test/tests/z_extensive/run.rs @@ -106,8 +106,7 @@ where let completed = AtomicU64::new(0); let ctx = CheckCtx::new(Op::IDENTIFIER, CheckBasis::Mpfr); - let cases = &mut extensive::get_test_cases::(&ctx); - let total: u64 = cases.len().try_into().unwrap(); + let (ref mut cases, total) = extensive::get_test_cases::(&ctx); let pb = Progress::new(Op::NAME, total); let test_single_chunk = |mp_vals: &mut Op::MpTy, input_vec: Vec| -> TestResult {