Skip to main content

perl_percentile/
lib.rs

1//! Percentile helpers for integer metric samples.
2//!
3//! This crate has a single responsibility: compute nearest-rank percentiles for
4//! pre-sorted `u64` samples.
5
6#![deny(unsafe_code)]
7#![warn(rust_2018_idioms)]
8#![warn(missing_docs)]
9#![warn(clippy::all)]
10
11/// Compute the nearest-rank percentile from a sorted sample slice.
12///
13/// The nearest-rank definition uses:
14///
15/// - `rank = ceil((pct / 100) * n)` where `n` is sample length.
16/// - returned value is sample at `rank - 1` (1-based to 0-based conversion).
17///
18/// # Parameters
19///
20/// - `sorted_values`: sample values sorted in ascending order.
21/// - `pct`: percentile in the range `0..=100`.
22///
23/// # Returns
24///
25/// - `0` when `sorted_values` is empty.
26/// - nearest-rank percentile value otherwise.
27#[must_use]
28pub fn nearest_rank_percentile(sorted_values: &[u64], pct: u64) -> u64 {
29    if sorted_values.is_empty() {
30        return 0;
31    }
32
33    let pct_clamped = pct.min(100);
34    let rank = ((pct_clamped as f64 / 100.0) * sorted_values.len() as f64).ceil() as usize;
35    sorted_values[rank.min(sorted_values.len()).saturating_sub(1)]
36}
37
38#[cfg(test)]
39mod tests {
40    use super::nearest_rank_percentile;
41
42    #[test]
43    fn nearest_rank_handles_empty_input() {
44        assert_eq!(nearest_rank_percentile(&[], 95), 0);
45    }
46
47    #[test]
48    fn nearest_rank_percentiles_match_expected_values() {
49        let sorted = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
50        assert_eq!(nearest_rank_percentile(&sorted, 50), 5);
51        assert_eq!(nearest_rank_percentile(&sorted, 95), 10);
52        assert_eq!(nearest_rank_percentile(&sorted, 99), 10);
53    }
54
55    #[test]
56    fn nearest_rank_clamps_high_percentiles() {
57        let sorted = [10, 20, 30];
58        assert_eq!(nearest_rank_percentile(&sorted, 1000), 30);
59    }
60}