Skip to content

Commit 6f72bab

Browse files
beetreestgross35
authored andcommitted
Make extensive tests exhaustive if there are enough iterations available
1 parent 9899d00 commit 6f72bab

File tree

5 files changed

+252
-81
lines changed

5 files changed

+252
-81
lines changed

libm/crates/libm-test/src/gen/domain_logspace.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@ where
2727
let start = domain.range_start();
2828
let end = domain.range_end();
2929
let steps = OpITy::<Op>::try_from(ntests).unwrap_or(OpITy::<Op>::MAX);
30-
logspace(start, end, steps).map(|v| (v,))
30+
logspace(start, end, steps).0.map(|v| (v,))
3131
}

libm/crates/libm-test/src/gen/extensive.rs

Lines changed: 171 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,79 @@
11
use std::fmt;
22
use std::ops::RangeInclusive;
33

4-
use libm::support::MinInt;
4+
use libm::support::{Float, MinInt};
55

66
use crate::domain::HasDomain;
7-
use crate::gen::KnownSize;
87
use crate::op::OpITy;
98
use crate::run_cfg::{int_range, iteration_count};
10-
use crate::{CheckCtx, GeneratorKind, MathOp, logspace};
9+
use crate::{CheckCtx, GeneratorKind, MathOp, linear_ints, logspace};
1110

1211
/// Generate a sequence of inputs that either cover the domain in completeness (for smaller float
1312
/// types and single argument functions) or provide evenly spaced inputs across the domain with
1413
/// approximately `u32::MAX` total iterations.
1514
pub trait ExtensiveInput<Op> {
16-
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> + Send;
15+
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self> + Send, u64);
1716
}
1817

1918
/// Construct an iterator from `logspace` and also calculate the total number of steps expected
2019
/// for that iterator.
2120
fn logspace_steps<Op>(
2221
start: Op::FTy,
2322
end: Op::FTy,
24-
ctx: &CheckCtx,
25-
argnum: usize,
23+
max_steps: u64,
2624
) -> (impl Iterator<Item = Op::FTy> + Clone, u64)
2725
where
2826
Op: MathOp,
2927
OpITy<Op>: TryFrom<u64, Error: fmt::Debug>,
28+
u64: TryFrom<OpITy<Op>, Error: fmt::Debug>,
3029
RangeInclusive<OpITy<Op>>: Iterator,
3130
{
32-
let max_steps = iteration_count(ctx, GeneratorKind::Extensive, argnum);
3331
let max_steps = OpITy::<Op>::try_from(max_steps).unwrap_or(OpITy::<Op>::MAX);
34-
let iter = logspace(start, end, max_steps);
32+
let (iter, steps) = logspace(start, end, max_steps);
33+
34+
// `steps` will be <= the original `max_steps`, which is a `u64`.
35+
(iter, steps.try_into().unwrap())
36+
}
37+
38+
/// Represents the iterator in either `Left` or `Right`.
39+
enum EitherIter<A, B> {
40+
A(A),
41+
B(B),
42+
}
3543

36-
// `logspace` can't implement `ExactSizeIterator` because of the range, but its size hint
37-
// should be accurate (assuming <= usize::MAX iterations).
38-
let size_hint = iter.size_hint();
39-
assert_eq!(size_hint.0, size_hint.1.unwrap());
44+
impl<T, A: Iterator<Item = T>, B: Iterator<Item = T>> Iterator for EitherIter<A, B> {
45+
type Item = T;
4046

41-
(iter, size_hint.0.try_into().unwrap())
47+
fn next(&mut self) -> Option<Self::Item> {
48+
match self {
49+
Self::A(iter) => iter.next(),
50+
Self::B(iter) => iter.next(),
51+
}
52+
}
53+
54+
fn size_hint(&self) -> (usize, Option<usize>) {
55+
match self {
56+
Self::A(iter) => iter.size_hint(),
57+
Self::B(iter) => iter.size_hint(),
58+
}
59+
}
60+
}
61+
62+
/// Gets the total number of possible values, returning `None` if that number doesn't fit in a
63+
/// `u64`.
64+
fn value_count<F: Float>() -> Option<u64>
65+
where
66+
u64: TryFrom<F::Int>,
67+
{
68+
u64::try_from(F::Int::MAX).ok().and_then(|max| max.checked_add(1))
69+
}
70+
71+
/// Returns an iterator of every possible value of type `F`.
72+
fn all_values<F: Float>() -> impl Iterator<Item = F>
73+
where
74+
RangeInclusive<F::Int>: Iterator<Item = F::Int>,
75+
{
76+
(F::Int::MIN..=F::Int::MAX).map(|bits| F::from_bits(bits))
4277
}
4378

4479
macro_rules! impl_extensive_input {
@@ -48,91 +83,161 @@ macro_rules! impl_extensive_input {
4883
Op: MathOp<RustArgs = Self, FTy = $fty>,
4984
Op: HasDomain<Op::FTy>,
5085
{
51-
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
52-
let start = Op::DOMAIN.range_start();
53-
let end = Op::DOMAIN.range_end();
54-
let (iter0, steps0) = logspace_steps::<Op>(start, end, ctx, 0);
55-
let iter0 = iter0.map(|v| (v,));
56-
KnownSize::new(iter0, steps0)
86+
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
87+
let max_steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0);
88+
// `f16` and `f32` can have exhaustive tests.
89+
match value_count::<Op::FTy>() {
90+
Some(steps0) if steps0 <= max_steps0 => {
91+
let iter0 = all_values();
92+
let iter0 = iter0.map(|v| (v,));
93+
(EitherIter::A(iter0), steps0)
94+
}
95+
_ => {
96+
let start = Op::DOMAIN.range_start();
97+
let end = Op::DOMAIN.range_end();
98+
let (iter0, steps0) = logspace_steps::<Op>(start, end, max_steps0);
99+
let iter0 = iter0.map(|v| (v,));
100+
(EitherIter::B(iter0), steps0)
101+
}
102+
}
57103
}
58104
}
59105

60106
impl<Op> ExtensiveInput<Op> for ($fty, $fty)
61107
where
62108
Op: MathOp<RustArgs = Self, FTy = $fty>,
63109
{
64-
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
65-
let start = <$fty>::NEG_INFINITY;
66-
let end = <$fty>::INFINITY;
67-
let (iter0, steps0) = logspace_steps::<Op>(start, end, ctx, 0);
68-
let (iter1, steps1) = logspace_steps::<Op>(start, end, ctx, 1);
69-
let iter =
70-
iter0.flat_map(move |first| iter1.clone().map(move |second| (first, second)));
71-
let count = steps0.checked_mul(steps1).unwrap();
72-
KnownSize::new(iter, count)
110+
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
111+
let max_steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0);
112+
let max_steps1 = iteration_count(ctx, GeneratorKind::Extensive, 1);
113+
// `f16` can have exhaustive tests.
114+
match value_count::<Op::FTy>() {
115+
Some(count) if count <= max_steps0 && count <= max_steps1 => {
116+
let iter = all_values()
117+
.flat_map(|first| all_values().map(move |second| (first, second)));
118+
(EitherIter::A(iter), count.checked_mul(count).unwrap())
119+
}
120+
_ => {
121+
let start = <$fty>::NEG_INFINITY;
122+
let end = <$fty>::INFINITY;
123+
let (iter0, steps0) = logspace_steps::<Op>(start, end, max_steps0);
124+
let (iter1, steps1) = logspace_steps::<Op>(start, end, max_steps1);
125+
let iter = iter0.flat_map(move |first| {
126+
iter1.clone().map(move |second| (first, second))
127+
});
128+
let count = steps0.checked_mul(steps1).unwrap();
129+
(EitherIter::B(iter), count)
130+
}
131+
}
73132
}
74133
}
75134

76135
impl<Op> ExtensiveInput<Op> for ($fty, $fty, $fty)
77136
where
78137
Op: MathOp<RustArgs = Self, FTy = $fty>,
79138
{
80-
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
81-
let start = <$fty>::NEG_INFINITY;
82-
let end = <$fty>::INFINITY;
83-
84-
let (iter0, steps0) = logspace_steps::<Op>(start, end, ctx, 0);
85-
let (iter1, steps1) = logspace_steps::<Op>(start, end, ctx, 1);
86-
let (iter2, steps2) = logspace_steps::<Op>(start, end, ctx, 2);
87-
88-
let iter = iter0
89-
.flat_map(move |first| iter1.clone().map(move |second| (first, second)))
90-
.flat_map(move |(first, second)| {
91-
iter2.clone().map(move |third| (first, second, third))
92-
});
93-
let count = steps0.checked_mul(steps1).unwrap().checked_mul(steps2).unwrap();
94-
95-
KnownSize::new(iter, count)
139+
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
140+
let max_steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0);
141+
let max_steps1 = iteration_count(ctx, GeneratorKind::Extensive, 1);
142+
let max_steps2 = iteration_count(ctx, GeneratorKind::Extensive, 2);
143+
// `f16` can be exhaustive tested if `LIBM_EXTENSIVE_TESTS` is incresed.
144+
match value_count::<Op::FTy>() {
145+
Some(count)
146+
if count <= max_steps0 && count <= max_steps1 && count <= max_steps2 =>
147+
{
148+
let iter = all_values().flat_map(|first| {
149+
all_values().flat_map(move |second| {
150+
all_values().map(move |third| (first, second, third))
151+
})
152+
});
153+
(EitherIter::A(iter), count.checked_pow(3).unwrap())
154+
}
155+
_ => {
156+
let start = <$fty>::NEG_INFINITY;
157+
let end = <$fty>::INFINITY;
158+
159+
let (iter0, steps0) = logspace_steps::<Op>(start, end, max_steps0);
160+
let (iter1, steps1) = logspace_steps::<Op>(start, end, max_steps1);
161+
let (iter2, steps2) = logspace_steps::<Op>(start, end, max_steps2);
162+
163+
let iter = iter0
164+
.flat_map(move |first| iter1.clone().map(move |second| (first, second)))
165+
.flat_map(move |(first, second)| {
166+
iter2.clone().map(move |third| (first, second, third))
167+
});
168+
let count =
169+
steps0.checked_mul(steps1).unwrap().checked_mul(steps2).unwrap();
170+
171+
(EitherIter::B(iter), count)
172+
}
173+
}
96174
}
97175
}
98176

99177
impl<Op> ExtensiveInput<Op> for (i32, $fty)
100178
where
101179
Op: MathOp<RustArgs = Self, FTy = $fty>,
102180
{
103-
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
104-
let start = <$fty>::NEG_INFINITY;
105-
let end = <$fty>::INFINITY;
181+
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
182+
let range0 = int_range(ctx, GeneratorKind::Extensive, 0);
183+
let max_steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0);
184+
let max_steps1 = iteration_count(ctx, GeneratorKind::Extensive, 1);
185+
match value_count::<Op::FTy>() {
186+
Some(count1) if count1 <= max_steps1 => {
187+
let (iter0, steps0) = linear_ints(range0, max_steps0);
188+
let iter = iter0
189+
.flat_map(move |first| all_values().map(move |second| (first, second)));
190+
(EitherIter::A(iter), steps0.checked_mul(count1).unwrap())
191+
}
192+
_ => {
193+
let start = <$fty>::NEG_INFINITY;
194+
let end = <$fty>::INFINITY;
106195

107-
let iter0 = int_range(ctx, GeneratorKind::Extensive, 0);
108-
let steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0);
109-
let (iter1, steps1) = logspace_steps::<Op>(start, end, ctx, 1);
196+
let (iter0, steps0) = linear_ints(range0, max_steps0);
197+
let (iter1, steps1) = logspace_steps::<Op>(start, end, max_steps1);
110198

111-
let iter =
112-
iter0.flat_map(move |first| iter1.clone().map(move |second| (first, second)));
113-
let count = steps0.checked_mul(steps1).unwrap();
199+
let iter = iter0.flat_map(move |first| {
200+
iter1.clone().map(move |second| (first, second))
201+
});
202+
let count = steps0.checked_mul(steps1).unwrap();
114203

115-
KnownSize::new(iter, count)
204+
(EitherIter::B(iter), count)
205+
}
206+
}
116207
}
117208
}
118209

119210
impl<Op> ExtensiveInput<Op> for ($fty, i32)
120211
where
121212
Op: MathOp<RustArgs = Self, FTy = $fty>,
122213
{
123-
fn get_cases(ctx: &CheckCtx) -> impl ExactSizeIterator<Item = Self> {
124-
let start = <$fty>::NEG_INFINITY;
125-
let end = <$fty>::INFINITY;
214+
fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
215+
let max_steps0 = iteration_count(ctx, GeneratorKind::Extensive, 0);
216+
let range1 = int_range(ctx, GeneratorKind::Extensive, 1);
217+
let max_steps1 = iteration_count(ctx, GeneratorKind::Extensive, 1);
218+
match value_count::<Op::FTy>() {
219+
Some(count0) if count0 <= max_steps0 => {
220+
let (iter1, steps1) = linear_ints(range1, max_steps1);
221+
let iter = all_values().flat_map(move |first| {
222+
iter1.clone().map(move |second| (first, second))
223+
});
224+
(EitherIter::A(iter), count0.checked_mul(steps1).unwrap())
225+
}
226+
_ => {
227+
let start = <$fty>::NEG_INFINITY;
228+
let end = <$fty>::INFINITY;
126229

127-
let (iter0, steps0) = logspace_steps::<Op>(start, end, ctx, 0);
128-
let iter1 = int_range(ctx, GeneratorKind::Extensive, 0);
129-
let steps1 = iteration_count(ctx, GeneratorKind::Extensive, 0);
230+
let (iter0, steps0) = logspace_steps::<Op>(start, end, max_steps0);
231+
let (iter1, steps1) = linear_ints(range1, max_steps1);
130232

131-
let iter =
132-
iter0.flat_map(move |first| iter1.clone().map(move |second| (first, second)));
133-
let count = steps0.checked_mul(steps1).unwrap();
233+
let iter = iter0.flat_map(move |first| {
234+
iter1.clone().map(move |second| (first, second))
235+
});
236+
let count = steps0.checked_mul(steps1).unwrap();
134237

135-
KnownSize::new(iter, count)
238+
(EitherIter::B(iter), count)
239+
}
240+
}
136241
}
137242
}
138243
};
@@ -145,10 +250,10 @@ impl_extensive_input!(f64);
145250
#[cfg(f128_enabled)]
146251
impl_extensive_input!(f128);
147252

148-
/// Create a test case iterator for extensive inputs.
253+
/// Create a test case iterator for extensive inputs. Also returns the total test case count.
149254
pub fn get_test_cases<Op>(
150255
ctx: &CheckCtx,
151-
) -> impl ExactSizeIterator<Item = Op::RustArgs> + Send + use<'_, Op>
256+
) -> (impl Iterator<Item = Op::RustArgs> + Send + use<'_, Op>, u64)
152257
where
153258
Op: MathOp,
154259
Op::RustArgs: ExtensiveInput<Op>,

libm/crates/libm-test/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use std::time::SystemTime;
2222

2323
pub use f8_impl::f8;
2424
pub use libm::support::{Float, Int, IntTy, MinInt};
25-
pub use num::{FloatExt, logspace};
25+
pub use num::{FloatExt, linear_ints, logspace};
2626
pub use op::{
2727
BaseName, FloatTy, Identifier, MathOp, OpCFn, OpCRet, OpFTy, OpRustFn, OpRustRet, Ty,
2828
};

0 commit comments

Comments
 (0)