dpp_rules/common/
numeric.rs1#[must_use]
5pub fn percentage_in_range(pct: f64) -> bool {
6 pct.is_finite() && (0.0..=100.0).contains(&pct)
7}
8
9#[must_use]
14pub fn sums_to(values: impl IntoIterator<Item = f64>, target: f64, tolerance: f64) -> (bool, f64) {
15 let total: f64 = values.into_iter().sum();
16 let within_tolerance = total.is_finite() && (total - target).abs() <= tolerance;
17 (within_tolerance, total)
18}
19
20#[cfg(test)]
21mod tests {
22 use super::*;
23
24 #[test]
25 fn percentage_in_range_accepts_boundaries() {
26 assert!(percentage_in_range(0.0));
27 assert!(percentage_in_range(100.0));
28 assert!(percentage_in_range(50.0));
29 }
30
31 #[test]
32 fn percentage_in_range_rejects_out_of_bounds_and_non_finite() {
33 assert!(!percentage_in_range(-0.001));
34 assert!(!percentage_in_range(100.001));
35 assert!(!percentage_in_range(f64::NAN));
36 assert!(!percentage_in_range(f64::INFINITY));
37 }
38
39 #[test]
40 fn sums_to_within_tolerance() {
41 let (ok, total) = sums_to([60.0, 40.0], 100.0, 2.0);
42 assert!(ok);
43 assert!((total - 100.0).abs() < f64::EPSILON);
44
45 let (ok, total) = sums_to([98.5, 1.0], 100.0, 2.0);
46 assert!(ok, "99.5 is within ±2 of 100");
47 assert!((total - 99.5).abs() < f64::EPSILON);
48 }
49
50 #[test]
51 fn sums_to_outside_tolerance() {
52 let (ok, total) = sums_to([60.0, 30.0], 100.0, 2.0);
53 assert!(!ok);
54 assert!((total - 90.0).abs() < f64::EPSILON);
55 }
56
57 #[test]
58 fn sums_to_non_finite_input_is_never_ok() {
59 let (ok, total) = sums_to([f64::NAN, 50.0], 100.0, 2.0);
60 assert!(!ok);
61 assert!(total.is_nan());
62 }
63
64 #[test]
65 fn sums_to_empty_is_zero() {
66 let (ok, total) = sums_to([], 100.0, 2.0);
67 assert!(!ok);
68 assert_eq!(total, 0.0);
69 }
70}