For 32 bit floats, you can skip the math and just test all of them. LLVM will vectorise and unroll this nicely.
fn main() {
let start = std::time::Instant::now();
let total = (0..=u32::MAX)
.filter(|&x| {
let f = f32::from_bits(x);
0. <= f && f <= 1.
})
.count();
println!("total {total} in {:?}", start.elapsed());
}
total 1065353218 in 1.364751583s
Edit: Apparently if you move the sum to its own function it runs in 500ms. A bit temperamental.
Edit 2: it's the size of the sum accumulator that makes it slow. The version above is like `.fold(0usize, |a, _| a + 1)`. When I moved it to another function, I cast the return value to u32, so LLVM saw basically `.fold(0u32, |a, _| a + 1)` and could use u32 throughout. Godbolt says the usize version ends up with floats in xmm* registers on x86, which fit 4 32-bit floats, but the u32 version ends up with floats in ymm* registers (8 32-bit floats) and similar half-as-wide behaviour on ARM.
Edit 2: it's the size of the sum accumulator that makes it slow. The version above is like `.fold(0usize, |a, _| a + 1)`. When I moved it to another function, I cast the return value to u32, so LLVM saw basically `.fold(0u32, |a, _| a + 1)` and could use u32 throughout. Godbolt says the usize version ends up with floats in xmm* registers on x86, which fit 4 32-bit floats, but the u32 version ends up with floats in ymm* registers (8 32-bit floats) and similar half-as-wide behaviour on ARM.