use crate::numeric::count_to_f64;
use crate::rng::SplitMix64;
pub(super) fn floor_rank(fraction: f64, n: usize) -> usize {
let target = fraction * count_to_f64(n);
let (mut lo, mut hi) = (0usize, n);
while lo < hi {
let mid = lo + (hi - lo).div_ceil(2);
if count_to_f64(mid) <= target {
lo = mid;
} else {
hi = mid - 1;
}
}
lo
}
pub(super) fn uniform_index(rng: &mut SplitMix64, n: usize) -> usize {
let span = u64::try_from(n).unwrap_or(u64::MAX);
let draw = rng.next_u64() % span;
usize::try_from(draw).unwrap_or(n - 1)
}
#[cfg(kani)]
mod verification {
use super::{SplitMix64, floor_rank, uniform_index};
const MAX_N: usize = 5;
#[kani::proof]
fn resampling_uniform_index_in_bounds() {
let state: u64 = kani::any();
let n: usize = kani::any();
kani::assume(n > 0);
kani::assume(n <= MAX_N);
let mut rng = SplitMix64::new(state);
let idx = uniform_index(&mut rng, n);
assert!(idx < n, "uniform_index escaped 0..n: {idx} >= {n}");
}
#[kani::proof]
#[kani::unwind(6)]
fn resampling_floor_rank_in_range() {
let fraction: f64 = kani::any();
kani::assume(fraction.is_finite());
kani::assume(fraction >= 0.0);
kani::assume(fraction <= 1.0);
let n: usize = kani::any();
kani::assume(n <= MAX_N);
let rank = floor_rank(fraction, n);
assert!(rank <= n, "floor_rank escaped 0..=n: {rank} > {n}");
}
}