use std::time::Duration;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PairedRatioConfidence {
pub sample_count: usize,
pub geometric_mean_ratio: f64,
pub low_ratio: f64,
pub high_ratio: f64,
}
pub fn midpoint_u128(lower: u128, upper: u128) -> u128 {
lower + (upper - lower) / 2
}
pub fn paired_ratio_confidence_95(
reference: &[Duration],
candidate: &[Duration],
) -> Option<PairedRatioConfidence> {
if reference.len() != candidate.len() || reference.len() < 2 {
return None;
}
let mut log_ratios = Vec::with_capacity(reference.len());
for (&reference_duration, &candidate_duration) in reference.iter().zip(candidate) {
let reference_secs = reference_duration.as_secs_f64();
let candidate_secs = candidate_duration.as_secs_f64();
if reference_secs <= 0.0 || candidate_secs <= 0.0 {
return None;
}
log_ratios.push((candidate_secs / reference_secs).ln());
}
let count = log_ratios.len() as f64;
let mean = log_ratios.iter().sum::<f64>() / count;
let variance = log_ratios
.iter()
.map(|ratio| {
let delta = ratio - mean;
delta * delta
})
.sum::<f64>()
/ (count - 1.0);
let half_width = two_sided_95_student_t_critical(log_ratios.len()) * (variance / count).sqrt();
Some(PairedRatioConfidence {
sample_count: log_ratios.len(),
geometric_mean_ratio: mean.exp(),
low_ratio: (mean - half_width).exp(),
high_ratio: (mean + half_width).exp(),
})
}
pub fn median_duration(samples: &[Duration]) -> Option<Duration> {
if samples.is_empty() {
return None;
}
let mut sorted = samples.to_vec();
sorted.sort_unstable();
let middle = sorted.len() / 2;
if sorted.len() % 2 == 1 {
Some(sorted[middle])
} else {
let lower = sorted[middle - 1];
let upper = sorted[middle];
let midpoint_nanos = midpoint_u128(lower.as_nanos(), upper.as_nanos());
let seconds = midpoint_nanos / 1_000_000_000;
let nanos = (midpoint_nanos % 1_000_000_000) as u32;
Some(Duration::new(seconds as u64, nanos))
}
}
pub fn two_sided_95_student_t_critical(sample_count: usize) -> f64 {
match sample_count {
0 | 1 => 0.0,
2 => 12.706_204_736,
3 => 4.302_652_73,
4 => 3.182_446_305,
5 => 2.776_445_105,
6 => 2.570_581_836,
7 => 2.446_911_851,
8 => 2.364_624_252,
9 => 2.306_004_135,
10 => 2.262_157_163,
11 => 2.228_138_852,
12 => 2.200_985_16,
13 => 2.178_812_83,
14 => 2.160_368_656,
15 => 2.144_786_688,
16 => 2.131_449_546,
17 => 2.119_905_299,
18 => 2.109_815_578,
19 => 2.100_922_04,
20 => 2.093_024_054,
21 => 2.085_963_447,
22 => 2.079_613_845,
23 => 2.073_873_068,
24 => 2.068_657_61,
25 => 2.063_898_562,
26 => 2.059_538_553,
27 => 2.055_529_439,
28 => 2.051_830_516,
29 => 2.048_407_142,
30 => 2.045_229_642,
31 => 2.042_272_456,
_ => 2.042_272_456,
}
}