libm-test: generator for varying specific bits#1182
libm-test: generator for varying specific bits#1182quaternic wants to merge 2 commits intorust-lang:mainfrom
Conversation
|
@tgross35 would produce the |
5e29c72 to
7faff1a
Compare
libm-test/src/generate/bitpattern.rs
Outdated
| /// An efficient equivalent to | ||
| /// `(0..=uN::MAX).filter(|x| x & !varying == 0).map(|x| x ^ preset)` | ||
| /// That is, "all integers that only differ from the preset in the varying bits" | ||
| pub struct BitConfig<I> { | ||
| /// A bitmask of bits to list exhaustively | ||
| varying: I, | ||
| /// Other bits are set according to preset. | ||
| preset: I, | ||
| } | ||
|
|
||
| impl<I: Int<Unsigned = I>> BitConfig<I> { | ||
| fn into_iter(self) -> impl Iterator<Item = I> + Clone { | ||
| assert!( | ||
| self.varying != I::MAX, | ||
| "to optimize the implementation, varying every bit is not supported" | ||
| ); | ||
| let fixed = !self.varying; | ||
| let flip = self.preset ^ fixed; | ||
| let mut counter = fixed - I::ONE; | ||
|
|
||
| // `(counter + 1) & !fixed` is initially 0, and increases after each item returned | ||
| std::iter::from_fn(move || { | ||
| counter = counter.checked_add(I::ONE)? | fixed; | ||
| Some(counter ^ flip) | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
Could this be turned into a single function with a signature like the following:
fn bit_sequence<I, F>(varying: I, preset: F) -> impl Iterator<Item = I> + Clone
where
I: Int,
F: FnMut() -> I + Clone;So the default could be created per-iteration with a rng?
There was a problem hiding this comment.
IIRC the only reason I had preset in there is that this way you can do the .map(|x| x ^ preset) "for free", but I now realize that a little bit of inlining should allow the optimizer to do that regardless. So it should be cleaner to just have
fn bitwise_subsets<I>(varying: I) -> impl Iterator<Item = I> + Clone
libm-test/src/generate/bitpattern.rs
Outdated
| let mut bit_priority: Vec<_> = (0..F::BITS).rev().collect(); | ||
| // sign bit first, otherwise by least distance to any edge of a bitfield, | ||
| bit_priority[1..].sort_by_key(|&i| { | ||
| // avoid a fencepost error by mapping the bit indices to odd numbers, | ||
| // and compare them to the bitfield edges mapped to even integers | ||
| let i = 2 * i + 1; | ||
| i.min(i.abs_diff(F::SIG_BITS * 2)) | ||
| .min(i.abs_diff((F::BITS - 1) * 2)) | ||
| }); | ||
|
|
||
| let varying = bit_priority[..bits_to_vary as usize] | ||
| .iter() | ||
| .map(|&i| F::Int::ONE << i) | ||
| .reduce(std::ops::BitOr::bitor) | ||
| .unwrap_or(F::Int::ZERO); |
There was a problem hiding this comment.
Could this portion be extracted to a separate function? The exact masks could then be asserted in tests.
It would be okay to drop the fillers portion or move it to tests since I think whatever wraps this for a test will need to do some tweaking around that.
libm-test/src/generate/bitpattern.rs
Outdated
| // sign bit first, otherwise by least distance to any edge of a bitfield, | ||
| bit_priority[1..].sort_by_key(|&i| { | ||
| // avoid a fencepost error by mapping the bit indices to odd numbers, | ||
| // and compare them to the bitfield edges mapped to even integers | ||
| let i = 2 * i + 1; | ||
| i.min(i.abs_diff(F::SIG_BITS * 2)) | ||
| .min(i.abs_diff((F::BITS - 1) * 2)) | ||
| }); |
There was a problem hiding this comment.
From a quick run this seems to prefer putting the next bit into the upper end of the significand rather than the lower. I was thinking lower would be better since that's more likely to run into rounding issues.
Think it could also be worth asserting that bits_to_vary >= 5 to sanity check that we're at least toggling a bit at the top and bottom of the significand (n=4 for f16 is 0b1100011000000000, n=5 is 0b1100011000000001).
f16 exp: 0b0111110000000000
f16 sig: 0b0000001111111111
ternary f16 mask: 0b1111111110000111
f32 exp: 0b01111111100000000000000000000000
f32 sig: 0b00000000011111111111111111111111
binary f32 mask: 0b11111111111111000000000000001111
ternary f32 mask: 0b11110011111100000000000000000011
f64 exp: 0b0111111111110000000000000000000000000000000000000000000000000000
f64 sig: 0b0000000000001111111111111111111111111111111111111111111111111111
unary f64 mask: 0b1111111111111111111111110000000000000000000000000000111111111111
binary f64 mask: 0b1111110011111111000000000000000000000000000000000000000000001111
ternary f64 mask: 0b1111000001111110000000000000000000000000000000000000000000000011
f128 exp: 0b01111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
f128 sig: 0b00000000000000001111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
unary f128 mask: 0b11111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111111
binary f128 mask: 0b11111100000011111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111
ternary f128 mask: 0b11110000000001111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011
libm-test/src/generate/bitpattern.rs
Outdated
| /// Biased generator for floats. | ||
| /// | ||
| /// The returned iterator will produce `fillers.len() << bits_to_vary` items. | ||
| pub fn float_gen<F>(bits_to_vary: u32, fillers: Vec<F::Int>) -> impl Iterator<Item = F> + Clone |
There was a problem hiding this comment.
For anything not yet used, remove the pub and mark it #[cfg_attr(not(test), expect(dead_code))] so we have a note of it.
Wow no kidding, that's pretty much identical. Are you interested in wiring up the tests in a followup? It would probably look like:
If so then it's of course appreciated, if not then it's just a todo list for me :) |
for #1181