1#![forbid(unsafe_code)]
4
5#[must_use]
7pub fn round_f64(value: f64, decimals: u32) -> f64 {
8 let factor = 10f64.powi(decimals as i32);
9 (value * factor).round() / factor
10}
11
12#[must_use]
14pub fn safe_ratio(numer: usize, denom: usize) -> f64 {
15 if denom == 0 {
16 0.0
17 } else {
18 round_f64(numer as f64 / denom as f64, 4)
19 }
20}
21
22#[must_use]
24pub fn percentile(sorted: &[usize], pct: f64) -> f64 {
25 if sorted.is_empty() {
26 return 0.0;
27 }
28 let idx = (pct * (sorted.len() as f64 - 1.0)).ceil() as usize;
29 sorted[idx.min(sorted.len() - 1)] as f64
30}
31
32#[must_use]
34pub fn gini_coefficient(sorted: &[usize]) -> f64 {
35 if sorted.is_empty() {
36 return 0.0;
37 }
38 let n = sorted.len() as f64;
39 let sum: f64 = sorted.iter().map(|v| *v as f64).sum();
40 if sum == 0.0 {
41 return 0.0;
42 }
43 let mut accum = 0.0;
44 for (i, value) in sorted.iter().enumerate() {
45 let i = i as f64 + 1.0;
46 accum += (2.0 * i - n - 1.0) * (*value as f64);
47 }
48 accum / (n * sum)
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 #[test]
56 fn round_f64_rounds_expected_precision() {
57 let value = 12.34567;
60 assert_eq!(round_f64(value, 2), 12.35);
61 assert_eq!(round_f64(value, 4), 12.3457);
62 }
63
64 #[test]
65 fn safe_ratio_guards_divide_by_zero() {
66 assert_eq!(safe_ratio(5, 0), 0.0);
67 assert_eq!(safe_ratio(1, 4), 0.25);
68 }
69
70 #[test]
71 fn percentile_returns_expected_values() {
72 let values = [10usize, 20, 30, 40, 50];
73 assert_eq!(percentile(&values, 0.0), 10.0);
74 assert_eq!(percentile(&values, 0.9), 50.0);
75 }
76
77 #[test]
78 fn gini_coefficient_handles_empty_and_uniform() {
79 assert_eq!(gini_coefficient(&[]), 0.0);
80 assert!((gini_coefficient(&[5, 5, 5, 5]) - 0.0).abs() < 1e-10);
81 }
82}