1use std::time::Duration;
4
5#[derive(Clone, Copy, Debug, PartialEq)]
7pub struct PairedRatioConfidence {
8 pub sample_count: usize,
10 pub geometric_mean_ratio: f64,
12 pub low_ratio: f64,
14 pub high_ratio: f64,
16}
17
18pub fn midpoint_u128(lower: u128, upper: u128) -> u128 {
20 lower + (upper - lower) / 2
21}
22
23pub fn paired_ratio_confidence_95(
29 reference: &[Duration],
30 candidate: &[Duration],
31) -> Option<PairedRatioConfidence> {
32 if reference.len() != candidate.len() || reference.len() < 2 {
33 return None;
34 }
35 let mut log_ratios = Vec::with_capacity(reference.len());
36 for (&reference_duration, &candidate_duration) in reference.iter().zip(candidate) {
37 let reference_secs = reference_duration.as_secs_f64();
38 let candidate_secs = candidate_duration.as_secs_f64();
39 if reference_secs <= 0.0 || candidate_secs <= 0.0 {
40 return None;
41 }
42 log_ratios.push((candidate_secs / reference_secs).ln());
43 }
44 let count = log_ratios.len() as f64;
45 let mean = log_ratios.iter().sum::<f64>() / count;
46 let variance = log_ratios
47 .iter()
48 .map(|ratio| {
49 let delta = ratio - mean;
50 delta * delta
51 })
52 .sum::<f64>()
53 / (count - 1.0);
54 let half_width = two_sided_95_student_t_critical(log_ratios.len()) * (variance / count).sqrt();
55 Some(PairedRatioConfidence {
56 sample_count: log_ratios.len(),
57 geometric_mean_ratio: mean.exp(),
58 low_ratio: (mean - half_width).exp(),
59 high_ratio: (mean + half_width).exp(),
60 })
61}
62
63pub fn median_duration(samples: &[Duration]) -> Option<Duration> {
66 if samples.is_empty() {
67 return None;
68 }
69 let mut sorted = samples.to_vec();
70 sorted.sort_unstable();
71 let middle = sorted.len() / 2;
72 if sorted.len() % 2 == 1 {
73 Some(sorted[middle])
74 } else {
75 let lower = sorted[middle - 1];
76 let upper = sorted[middle];
77 let midpoint_nanos = midpoint_u128(lower.as_nanos(), upper.as_nanos());
78 let seconds = midpoint_nanos / 1_000_000_000;
79 let nanos = (midpoint_nanos % 1_000_000_000) as u32;
80 Some(Duration::new(seconds as u64, nanos))
81 }
82}
83
84pub fn two_sided_95_student_t_critical(sample_count: usize) -> f64 {
86 match sample_count {
87 0 | 1 => 0.0,
88 2 => 12.706_204_736,
89 3 => 4.302_652_73,
90 4 => 3.182_446_305,
91 5 => 2.776_445_105,
92 6 => 2.570_581_836,
93 7 => 2.446_911_851,
94 8 => 2.364_624_252,
95 9 => 2.306_004_135,
96 10 => 2.262_157_163,
97 11 => 2.228_138_852,
98 12 => 2.200_985_16,
99 13 => 2.178_812_83,
100 14 => 2.160_368_656,
101 15 => 2.144_786_688,
102 16 => 2.131_449_546,
103 17 => 2.119_905_299,
104 18 => 2.109_815_578,
105 19 => 2.100_922_04,
106 20 => 2.093_024_054,
107 21 => 2.085_963_447,
108 22 => 2.079_613_845,
109 23 => 2.073_873_068,
110 24 => 2.068_657_61,
111 25 => 2.063_898_562,
112 26 => 2.059_538_553,
113 27 => 2.055_529_439,
114 28 => 2.051_830_516,
115 29 => 2.048_407_142,
116 30 => 2.045_229_642,
117 31 => 2.042_272_456,
118 _ => 2.042_272_456,
119 }
120}