pub fn percentile(sorted: &[u64], q: f64) -> u64 {
if sorted.is_empty() {
return 0;
}
let idx = ((q * sorted.len() as f64) as usize).min(sorted.len() - 1);
sorted[idx]
}
pub fn percentile_sweep(samples: &[u64], start: f64, end: f64, step: f64) -> Vec<(f64, u64)> {
if samples.is_empty() || step <= 0.0 {
return Vec::new();
}
let mut sorted = samples.to_vec();
sorted.sort_unstable();
let mut out = Vec::new();
let mut q = start;
while q <= end + 1e-9 {
out.push((q, percentile(&sorted, q)));
q += step;
}
out
}
pub fn mean(samples: &[u64]) -> u64 {
if samples.is_empty() {
return 0;
}
samples.iter().sum::<u64>() / samples.len() as u64
}
pub fn stddev(samples: &[u64]) -> u64 {
let n = samples.len();
if n < 2 {
return 0;
}
let mean_f = mean(samples) as f64;
let mut variance = 0.0f64;
for &v in samples {
let d = v as f64 - mean_f;
variance += d * d;
}
variance /= (n - 1) as f64;
variance.sqrt().round() as u64
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn percentile_empty_is_zero() {
assert_eq!(percentile(&[], 0.5), 0);
}
#[test]
fn percentile_known_distribution() {
let mut v: Vec<u64> = (0..100).collect();
v.sort_unstable();
assert_eq!(percentile(&v, 0.50), 50);
assert_eq!(percentile(&v, 0.99), 99);
assert_eq!(percentile(&v, 1.0), 99);
}
#[test]
fn percentile_sweep_endpoints_included() {
let v: Vec<u64> = (0..100).collect();
let sweep = percentile_sweep(&v, 0.0, 1.0, 0.5);
assert_eq!(sweep.len(), 3);
assert_eq!(sweep[0].0, 0.0);
assert_eq!(sweep[2].0, 1.0);
}
#[test]
fn percentile_sweep_rejects_zero_step() {
let v: Vec<u64> = (0..100).collect();
assert!(percentile_sweep(&v, 0.0, 1.0, 0.0).is_empty());
}
#[test]
fn mean_stddev_basic() {
let samples = vec![100u64, 200, 300, 400];
assert_eq!(mean(&samples), 250);
let sd = stddev(&samples);
assert!((125..=135).contains(&sd), "stddev around 129: {}", sd);
}
#[test]
fn mean_empty_zero() {
assert_eq!(mean(&[]), 0);
}
#[test]
fn stddev_single_sample_zero() {
assert_eq!(stddev(&[42]), 0);
}
}