Skip to main content

sim_lib_stream_clock/
instant.rs

1use sim_kernel::{Error, Result};
2
3/// Exact, non-negative point in time held as a reduced rational number of
4/// seconds.
5///
6/// Clock conversions need a time base that is free of floating-point rounding,
7/// so an `Instant` stores a numerator and denominator (both in seconds) and
8/// keeps them in lowest terms with a positive denominator. Stream instants are
9/// always non-negative; constructing a negative value fails closed.
10///
11/// # Examples
12///
13/// ```
14/// use sim_lib_stream_clock::Instant;
15///
16/// let half = Instant::new(1, 2)?;
17/// let one = Instant::seconds(1);
18/// let sum = one.checked_add(half)?;
19/// assert_eq!(sum.numerator(), 3);
20/// assert_eq!(sum.denominator(), 2);
21/// # Ok::<(), sim_kernel::Error>(())
22/// ```
23#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
24pub struct Instant {
25    numerator: i128,
26    denominator: i128,
27}
28
29impl Instant {
30    /// Builds an instant of `numerator / denominator` seconds, reduced to
31    /// lowest terms with a positive denominator.
32    ///
33    /// Returns an error when `denominator` is zero, when the reduced value
34    /// would be negative, or when normalizing the sign overflows `i128`.
35    pub fn new(numerator: i128, denominator: i128) -> Result<Self> {
36        if denominator == 0 {
37            return Err(Error::Eval(
38                "instant denominator must be non-zero".to_owned(),
39            ));
40        }
41        let mut numerator = numerator;
42        let mut denominator = denominator;
43        if denominator < 0 {
44            numerator = numerator
45                .checked_neg()
46                .ok_or_else(|| Error::Eval("instant numerator overflowed".to_owned()))?;
47            denominator = denominator
48                .checked_neg()
49                .ok_or_else(|| Error::Eval("instant denominator overflowed".to_owned()))?;
50        }
51        if numerator < 0 {
52            return Err(Error::Eval(
53                "stream instants must be non-negative".to_owned(),
54            ));
55        }
56        let divisor = gcd(numerator, denominator);
57        Ok(Self {
58            numerator: numerator / divisor,
59            denominator: denominator / divisor,
60        })
61    }
62
63    /// Returns the instant for a whole number of `seconds`.
64    pub fn seconds(seconds: u64) -> Self {
65        Self {
66            numerator: i128::from(seconds),
67            denominator: 1,
68        }
69    }
70
71    /// Returns the numerator of the reduced seconds fraction.
72    pub fn numerator(self) -> i128 {
73        self.numerator
74    }
75
76    /// Returns the (always positive) denominator of the reduced seconds
77    /// fraction.
78    pub fn denominator(self) -> i128 {
79        self.denominator
80    }
81
82    /// Returns the sum of `self` and `other`, reduced to lowest terms.
83    ///
84    /// Returns an error when the cross-multiplied addition overflows `i128`.
85    pub fn checked_add(self, other: Self) -> Result<Self> {
86        let numerator = self
87            .numerator
88            .checked_mul(other.denominator)
89            .and_then(|left| {
90                other
91                    .numerator
92                    .checked_mul(self.denominator)
93                    .and_then(|right| left.checked_add(right))
94            })
95            .ok_or_else(|| Error::Eval("instant addition overflowed".to_owned()))?;
96        let denominator = self
97            .denominator
98            .checked_mul(other.denominator)
99            .ok_or_else(|| Error::Eval("instant denominator overflowed".to_owned()))?;
100        Self::new(numerator, denominator)
101    }
102
103    /// Returns the difference `self - other`, reduced to lowest terms.
104    ///
105    /// Returns an error when the subtraction overflows `i128` or when the
106    /// result would be negative (stream instants are non-negative).
107    pub fn checked_sub(self, other: Self) -> Result<Self> {
108        let numerator = self
109            .numerator
110            .checked_mul(other.denominator)
111            .and_then(|left| {
112                other
113                    .numerator
114                    .checked_mul(self.denominator)
115                    .and_then(|right| left.checked_sub(right))
116            })
117            .ok_or_else(|| Error::Eval("instant subtraction overflowed".to_owned()))?;
118        let denominator = self
119            .denominator
120            .checked_mul(other.denominator)
121            .ok_or_else(|| Error::Eval("instant denominator overflowed".to_owned()))?;
122        Self::new(numerator, denominator)
123    }
124}
125
126fn gcd(mut left: i128, mut right: i128) -> i128 {
127    left = left.abs();
128    right = right.abs();
129    while right != 0 {
130        let remainder = left % right;
131        left = right;
132        right = remainder;
133    }
134    left.max(1)
135}