Skip to content

Commit 7faff1a

Browse files
committed
libm-test: generator for varying specific bits
1 parent 8b6760d commit 7faff1a

File tree

2 files changed

+134
-0
lines changed

2 files changed

+134
-0
lines changed

libm-test/src/generate.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Different generators that can create random or systematic bit patterns.
22
3+
pub mod bitpattern;
34
pub mod case_list;
45
pub mod edge_cases;
56
pub mod random;
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
use libm::support::{Float, Int, MinInt};
2+
3+
/// An efficient equivalent to
4+
/// `(0..=uN::MAX).filter(|x| x & !varying == 0).map(|x| x ^ preset)`
5+
/// That is, "all integers that only differ from the preset in the varying bits"
6+
pub struct BitConfig<I> {
7+
/// A bitmask of bits to list exhaustively
8+
varying: I,
9+
/// Other bits are set according to preset.
10+
preset: I,
11+
}
12+
13+
impl<I: Int<Unsigned = I>> BitConfig<I> {
14+
fn into_iter(self) -> impl Iterator<Item = I> + Clone {
15+
assert!(
16+
self.varying != I::MAX,
17+
"to optimize the implementation, varying every bit is not supported"
18+
);
19+
let fixed = !self.varying;
20+
let flip = self.preset ^ fixed;
21+
let mut counter = fixed - I::ONE;
22+
23+
// `(counter + 1) & !fixed` is initially 0, and increases after each item returned
24+
std::iter::from_fn(move || {
25+
counter = counter.checked_add(I::ONE)? | fixed;
26+
Some(counter ^ flip)
27+
})
28+
}
29+
}
30+
31+
/// Biased generator for floats.
32+
///
33+
/// The returned iterator will produce `fillers.len() << bits_to_vary` items.
34+
pub fn float_gen<F>(bits_to_vary: u32, fillers: Vec<F::Int>) -> impl Iterator<Item = F> + Clone
35+
where
36+
F: Float,
37+
F::Int: Int<Unsigned = F::Int>,
38+
{
39+
assert!(bits_to_vary < F::Int::BITS);
40+
41+
let mut bit_priority: Vec<_> = (0..F::BITS).rev().collect();
42+
// sign bit first, otherwise by least distance to any edge of a bitfield,
43+
bit_priority[1..].sort_by_key(|&i| {
44+
// avoid a fencepost error by mapping the bit indices to odd numbers,
45+
// and compare them to the bitfield edges mapped to even integers
46+
let i = 2 * i + 1;
47+
i.min(i.abs_diff(F::SIG_BITS * 2))
48+
.min(i.abs_diff((F::BITS - 1) * 2))
49+
});
50+
51+
let varying = bit_priority[..bits_to_vary as usize]
52+
.iter()
53+
.map(|&i| F::Int::ONE << i)
54+
.reduce(std::ops::BitOr::bitor)
55+
.unwrap_or(F::Int::ZERO);
56+
57+
fillers
58+
.into_iter()
59+
.map(move |preset| BitConfig {
60+
preset: preset & !varying,
61+
varying,
62+
})
63+
.flat_map(BitConfig::into_iter)
64+
.map(F::from_bits)
65+
}
66+
67+
#[cfg(test)]
68+
mod test {
69+
use super::{BitConfig, float_gen};
70+
#[test]
71+
fn equivalence() {
72+
// with a small integer type, we can easily verify that behaviour matches for all arguments
73+
for varying in 0..u8::MAX {
74+
for preset in 0..=u8::MAX {
75+
let expect = (0..=u8::MAX)
76+
.filter(|x| x & !varying == 0)
77+
.map(|x| x ^ preset);
78+
let iter = BitConfig { varying, preset }.into_iter();
79+
assert!(iter.eq(expect));
80+
}
81+
}
82+
}
83+
#[test]
84+
fn gen_includes_specials() {
85+
let v: Vec<_> = float_gen(5, vec![0, 0x7fffff, !0x7fffff, !0])
86+
.map(f32::to_bits)
87+
.collect();
88+
for x in &[
89+
0.0,
90+
f32::from_bits(1),
91+
f32::MIN_POSITIVE,
92+
f32::MAX,
93+
f32::INFINITY,
94+
f32::NAN,
95+
] {
96+
assert!(v.contains(&x.to_bits()), "{x} not found");
97+
assert!(v.contains(&(-x).to_bits()), "-{x} not found");
98+
}
99+
}
100+
#[test]
101+
fn count() {
102+
for k in 0..10 {
103+
assert!(float_gen::<f32>(k, vec![0]).count() == 1 << k);
104+
assert!(float_gen::<f32>(k, vec![0, !0]).count() == 2 << k);
105+
assert!(float_gen::<f32>(k, vec![0, 1, 2]).count() == 3 << k);
106+
}
107+
}
108+
#[test]
109+
fn specific() {
110+
let iter = float_gen::<f32>(1, vec![0]).map(f32::to_bits);
111+
assert!(iter.eq([0.0_f32.to_bits(), (-0.0_f32).to_bits()]));
112+
let iter = float_gen::<f64>(1, vec![0]).map(f64::to_bits);
113+
assert!(iter.eq([0.0_f64.to_bits(), (-0.0_f64).to_bits()]));
114+
115+
let mut v: Vec<_> = float_gen::<f32>(5, vec![0]).map(f32::to_bits).collect();
116+
assert!(v.len() == 32);
117+
assert!(v.is_sorted());
118+
v.dedup();
119+
assert!(v.len() == 32);
120+
for bits in v {
121+
assert!(bits & 0xc0c0_0001 == bits);
122+
}
123+
124+
let mut v: Vec<_> = float_gen::<f64>(5, vec![0]).map(f64::to_bits).collect();
125+
assert!(v.len() == 32);
126+
assert!(v.is_sorted());
127+
v.dedup();
128+
assert!(v.len() == 32);
129+
for bits in v {
130+
assert!(bits & 0xc018_0000_0000_0001 == bits);
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)