#[must_use]
pub(crate) fn percentile_linear(values: &[f64], p: f64) -> f64 {
assert!(!values.is_empty());
let n = values.len();
if n == 1 {
return values[0];
}
let p = p.clamp(0.0, 1.0);
let virtual_index = (n - 1) as f64 * p;
let lower = virtual_index.floor() as usize;
let upper = virtual_index.ceil() as usize;
if lower == upper {
return values[lower];
}
let fraction = virtual_index - lower as f64;
values[lower] + fraction * (values[upper] - values[lower])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn linear_percentile_matches_numpy_style() {
let mut values = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
values.sort_by(|a, b| a.partial_cmp(b).unwrap());
let p90 = percentile_linear(&values, 0.9);
assert!((p90 - 9.1).abs() < 1e-9);
let p50 = percentile_linear(&values, 0.5);
assert!((p50 - 5.5).abs() < 1e-9);
}
#[test]
fn single_element_percentile() {
assert_eq!(percentile_linear(&[42.0], 0.9), 42.0);
}
}