1use crate::percentiles::percentile;
9
10pub fn bootstrap_percentile_ci(
19 samples: &[u64],
20 q: f64,
21 iters: usize,
22 confidence: f64,
23 seed: u64,
24) -> (u64, u64) {
25 if samples.is_empty() || iters == 0 {
26 return (0, 0);
27 }
28 let mut state = seed | 1;
29 let mut estimates = Vec::with_capacity(iters);
30 for _ in 0..iters {
31 let mut resample = Vec::with_capacity(samples.len());
32 for _ in 0..samples.len() {
33 state = state
34 .wrapping_mul(6364136223846793005)
35 .wrapping_add(1442695040888963407);
36 let idx = (state as usize) % samples.len();
37 resample.push(samples[idx]);
38 }
39 resample.sort_unstable();
40 estimates.push(percentile(&resample, q));
41 }
42 estimates.sort_unstable();
43 let lo_q = (1.0 - confidence) / 2.0;
44 let hi_q = 1.0 - lo_q;
45 (percentile(&estimates, lo_q), percentile(&estimates, hi_q))
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 #[test]
53 fn p99_ci_brackets_point_estimate() {
54 let v: Vec<u64> = (0..1000).collect();
55 let (lo, hi) = bootstrap_percentile_ci(&v, 0.99, 200, 0.95, 42);
56 let point = {
57 let mut s = v.clone();
58 s.sort_unstable();
59 percentile(&s, 0.99)
60 };
61 assert!(lo <= point && point <= hi);
62 }
63
64 #[test]
65 fn empty_returns_zero_pair() {
66 assert_eq!(bootstrap_percentile_ci(&[], 0.99, 100, 0.95, 0), (0, 0));
67 }
68
69 #[test]
70 fn deterministic_under_same_seed() {
71 let v: Vec<u64> = (0..200).collect();
72 let a = bootstrap_percentile_ci(&v, 0.99, 100, 0.95, 7);
73 let b = bootstrap_percentile_ci(&v, 0.99, 100, 0.95, 7);
74 assert_eq!(a, b);
75 }
76
77 #[test]
78 fn zero_iters_returns_zero_pair() {
79 let v: Vec<u64> = (0..100).collect();
80 assert_eq!(bootstrap_percentile_ci(&v, 0.99, 0, 0.95, 0), (0, 0));
81 }
82}