//! Three ways to filter a slice of `f64` values, from idiomatic to branchless. //! //! Companion code for the article "Branchless Rust" by Serhii Potapov. /// The idiomatic version: iterate, filter, collect. pub fn filter_iter(input: &[f64], threshold: f64) -> Vec { input.iter().copied().filter(|&x| x > threshold).collect() } /// Preallocates the output vector to avoid reallocations along the way. pub fn filter_prealloc(input: &[f64], threshold: f64) -> Vec { let mut out = Vec::with_capacity(input.len()); for &x in input { if x > threshold { out.push(x); } } out } /// The branchless version: always write, conditionally advance the cursor. pub fn filter_branchless(input: &[f64], threshold: f64) -> Vec { let mut out = vec![0.0; input.len()]; let mut n = 0; for &x in input { out[n] = x; n += (x > threshold) as usize; } out.truncate(n); out } #[cfg(test)] mod tests { use super::*; use rand::{RngExt, SeedableRng, rngs::StdRng}; #[test] fn all_versions_agree() { let mut rng = StdRng::seed_from_u64(42); let input: Vec = (0..10_000).map(|_| rng.random_range(0.0..100.0)).collect(); for threshold in [-1.0, 0.0, 1.0, 50.0, 99.0, 100.0] { let expected = filter_iter(&input, threshold); assert_eq!(filter_prealloc(&input, threshold), expected); assert_eq!(filter_branchless(&input, threshold), expected); } } #[test] fn empty_input() { assert!(filter_iter(&[], 50.0).is_empty()); assert!(filter_prealloc(&[], 50.0).is_empty()); assert!(filter_branchless(&[], 50.0).is_empty()); } }