monitrs_core/units/
rate.rs1use core::fmt;
4
5#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[cfg_attr(feature = "serde", serde(transparent))]
16pub struct Rate(f64);
17
18impl Rate {
19 pub const ZERO: Self = Self(0.0);
21
22 #[must_use]
24 pub fn new(per_second: f64) -> Option<Self> {
25 if per_second.is_finite() && per_second >= 0.0 {
26 Some(Self(per_second))
27 } else {
28 None
29 }
30 }
31
32 #[must_use]
39 pub fn from_delta(delta: u64, elapsed: core::time::Duration) -> Option<Self> {
40 let seconds = elapsed.as_secs_f64();
41 if seconds <= 0.0 {
42 return None;
43 }
44 Self::new(delta as f64 / seconds)
45 }
46
47 #[must_use]
49 pub const fn per_second(self) -> f64 {
50 self.0
51 }
52
53 #[must_use]
55 pub fn delta_from(self, other: Self) -> f64 {
56 self.0 - other.0
57 }
58}
59
60impl fmt::Display for Rate {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 write!(f, "{:.0}/s", self.0)
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use core::time::Duration;
70
71 #[test]
72 fn rejects_non_finite_and_negative() {
73 assert!(Rate::new(-1.0).is_none());
74 assert!(Rate::new(f64::NAN).is_none());
75 assert!(Rate::new(f64::INFINITY).is_none());
76 }
77
78 #[test]
79 fn zero_elapsed_yields_no_rate_instead_of_infinity() {
80 assert!(Rate::from_delta(1024, Duration::ZERO).is_none());
81 }
82
83 #[test]
84 fn uses_actual_elapsed_time_not_an_assumed_second() {
85 let half = Rate::from_delta(1000, Duration::from_millis(500)).expect("valid");
86 assert!((half.per_second() - 2000.0).abs() < f64::EPSILON);
87 let double = Rate::from_delta(1000, Duration::from_secs(2)).expect("valid");
88 assert!((double.per_second() - 500.0).abs() < f64::EPSILON);
89 }
90
91 #[test]
92 fn a_zero_delta_is_a_real_zero_rate() {
93 let r = Rate::from_delta(0, Duration::from_secs(1)).expect("valid");
94 assert_eq!(r, Rate::ZERO);
95 }
96}