Make extensive tests exhaustive if there are enough iterations available
This commit is contained in:
@@ -27,5 +27,5 @@ where
|
||||
let start = domain.range_start();
|
||||
let end = domain.range_end();
|
||||
let steps = OpITy::<Op>::try_from(ntests).unwrap_or(OpITy::<Op>::MAX);
|
||||
logspace(start, end, steps).map(|v| (v,))
|
||||
logspace(start, end, steps).0.map(|v| (v,))
|
||||
}
|
||||
|
||||
@@ -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<Op> {
|
||||
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> + Send;
|
||||
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self> + Send, u64);
|
||||
}
|
||||
|
||||
/// Construct an iterator from `logspace` and also calculate the total number of steps expected
|
||||
@@ -21,24 +20,60 @@ pub trait ExtensiveInput<Op> {
|
||||
fn logspace_steps<Op>(
|
||||
start: Op::FTy,
|
||||
end: Op::FTy,
|
||||
ctx: &CheckCtx,
|
||||
argnum: usize,
|
||||
max_steps: u64,
|
||||
) -> (impl Iterator<Item = Op::FTy> + Clone, u64)
|
||||
where
|
||||
Op: MathOp,
|
||||
OpITy<Op>: TryFrom<u64, Error: fmt::Debug>,
|
||||
u64: TryFrom<OpITy<Op>, Error: fmt::Debug>,
|
||||
RangeInclusive<OpITy<Op>>: Iterator,
|
||||
{
|
||||
let max_steps = iteration_count(ctx, GeneratorKind::Extensive, argnum);
|
||||
let max_steps = OpITy::<Op>::try_from(max_steps).unwrap_or(OpITy::<Op>::MAX);
|
||||
let iter = logspace(start, end, max_steps);
|
||||
let (iter, steps) = logspace(start, end, max_steps);
|
||||
|
||||
// `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());
|
||||
// `steps` will be <= the original `max_steps`, which is a `u64`.
|
||||
(iter, steps.try_into().unwrap())
|
||||
}
|
||||
|
||||
(iter, size_hint.0.try_into().unwrap())
|
||||
/// Represents the iterator in either `Left` or `Right`.
|
||||
enum EitherIter<A, B> {
|
||||
A(A),
|
||||
B(B),
|
||||
}
|
||||
|
||||
impl<T, A: Iterator<Item = T>, B: Iterator<Item = T>> Iterator for EitherIter<A, B> {
|
||||
type Item = T;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Self::A(iter) => iter.next(),
|
||||
Self::B(iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
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<F: Float>() -> Option<u64>
|
||||
where
|
||||
u64: TryFrom<F::Int>,
|
||||
{
|
||||
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<F: Float>() -> impl Iterator<Item = F>
|
||||
where
|
||||
RangeInclusive<F::Int>: Iterator<Item = F::Int>,
|
||||
{
|
||||
(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<RustArgs = Self, FTy = $fty>,
|
||||
Op: HasDomain<Op::FTy>,
|
||||
{
|
||||
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
|
||||
let start = Op::DOMAIN.range_start();
|
||||
let end = Op::DOMAIN.range_end();
|
||||
let (iter0, steps0) = logspace_steps::<Op>(start, end, ctx, 0);
|
||||
let iter0 = iter0.map(|v| (v,));
|
||||
KnownSize::new(iter0, steps0)
|
||||
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
|
||||
let max_steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0);
|
||||
// `f16` and `f32` can have exhaustive tests.
|
||||
match value_count::<Op::FTy>() {
|
||||
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::<Op>(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<RustArgs = Self, FTy = $fty>,
|
||||
{
|
||||
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
|
||||
let start = <$fty>::NEG_INFINITY;
|
||||
let end = <$fty>::INFINITY;
|
||||
let (iter0, steps0) = logspace_steps::<Op>(start, end, ctx, 0);
|
||||
let (iter1, steps1) = logspace_steps::<Op>(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<Item = Self>, 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::<Op::FTy>() {
|
||||
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::<Op>(start, end, max_steps0);
|
||||
let (iter1, steps1) = logspace_steps::<Op>(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<RustArgs = Self, FTy = $fty>,
|
||||
{
|
||||
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
|
||||
let start = <$fty>::NEG_INFINITY;
|
||||
let end = <$fty>::INFINITY;
|
||||
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, 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::<Op::FTy>() {
|
||||
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::<Op>(start, end, ctx, 0);
|
||||
let (iter1, steps1) = logspace_steps::<Op>(start, end, ctx, 1);
|
||||
let (iter2, steps2) = logspace_steps::<Op>(start, end, ctx, 2);
|
||||
let (iter0, steps0) = logspace_steps::<Op>(start, end, max_steps0);
|
||||
let (iter1, steps1) = logspace_steps::<Op>(start, end, max_steps1);
|
||||
let (iter2, steps2) = logspace_steps::<Op>(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();
|
||||
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)
|
||||
(EitherIter::B(iter), count)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,19 +178,32 @@ macro_rules! impl_extensive_input {
|
||||
where
|
||||
Op: MathOp<RustArgs = Self, FTy = $fty>,
|
||||
{
|
||||
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
|
||||
let start = <$fty>::NEG_INFINITY;
|
||||
let end = <$fty>::INFINITY;
|
||||
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, 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::<Op::FTy>() {
|
||||
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::<Op>(start, end, ctx, 1);
|
||||
let (iter0, steps0) = linear_ints(range0, max_steps0);
|
||||
let (iter1, steps1) = logspace_steps::<Op>(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<RustArgs = Self, FTy = $fty>,
|
||||
{
|
||||
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
|
||||
let start = <$fty>::NEG_INFINITY;
|
||||
let end = <$fty>::INFINITY;
|
||||
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, 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::<Op::FTy>() {
|
||||
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::<Op>(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::<Op>(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<Op>(
|
||||
ctx: &CheckCtx,
|
||||
) -> impl ExactSizeIterator<Item = Op::RustArgs> + Send + use<'_, Op>
|
||||
) -> (impl Iterator<Item = Op::RustArgs> + Send + use<'_, Op>, u64)
|
||||
where
|
||||
Op: MathOp,
|
||||
Op::RustArgs: ExtensiveInput<Op>,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -215,7 +215,13 @@ fn as_ulp_steps<F: Float>(x: F) -> Option<F::SignedInt> {
|
||||
/// to logarithmic spacing of their values.
|
||||
///
|
||||
/// Note that this tends to skip negative zero, so that needs to be checked explicitly.
|
||||
pub fn logspace<F: FloatExt>(start: F, end: F, steps: F::Int) -> impl Iterator<Item = F> + Clone
|
||||
///
|
||||
/// Returns `(iterator, iterator_length)`.
|
||||
pub fn logspace<F: FloatExt>(
|
||||
start: F,
|
||||
end: F,
|
||||
steps: F::Int,
|
||||
) -> (impl Iterator<Item = F> + Clone, F::Int)
|
||||
where
|
||||
RangeInclusive<F::Int>: 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<i32>,
|
||||
steps: u64,
|
||||
) -> (impl Iterator<Item = i32> + 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]
|
||||
|
||||
Reference in New Issue
Block a user