embedded_time/
fraction.rs

1//! Fractional/Rational values
2use crate::ConversionError;
3use core::ops;
4use num::{rational::Ratio, CheckedDiv, CheckedMul, Zero};
5
6/// A fractional value
7///
8/// Used primarily to define the _scaling factor_ for the [`Duration`], [`Rate`], [`Instant`] and
9/// [`Clock`] traits and types.
10///
11/// [`Duration`]: duration/trait.Duration.html
12/// [`Rate`]: rate/trait.Rate.html
13/// [`Clock`]: clock/trait.Clock.html
14/// [`Instant`]: instant/struct.Instant.html
15#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
16pub struct Fraction(Ratio<u32>);
17
18impl Fraction {
19    /// Construct a new `Fraction`.
20    ///
21    /// A reduction is **not** performed. Also there is no check for a denominator of `0`. If these
22    /// features are needed, use [`Fraction::new_reduce()`]
23    pub const fn new(numerator: u32, denominator: u32) -> Self {
24        Self(Ratio::new_raw(numerator, denominator))
25    }
26
27    /// Return the numerator of the fraction
28    pub const fn numerator(&self) -> &u32 {
29        self.0.numer()
30    }
31
32    /// Return the denominator of the fraction
33    pub const fn denominator(&self) -> &u32 {
34        self.0.denom()
35    }
36}
37
38impl Fraction {
39    /// Construct a new `Fraction`.
40    ///
41    /// A reduction and `denominator == 0` check **are** performed.
42    ///
43    /// # Errors
44    ///
45    /// [`ConversionError::DivByZero`] : A `0` denominator was detected
46    // TODO: add example
47    pub fn new_reduce(numerator: u32, denominator: u32) -> Result<Self, ConversionError> {
48        if !denominator.is_zero() {
49            Ok(Self(Ratio::new(numerator, denominator)))
50        } else {
51            Err(ConversionError::DivByZero)
52        }
53    }
54
55    /// Returns the value truncated to an integer
56    pub fn to_integer(&self) -> u32 {
57        self.0.to_integer()
58    }
59
60    /// Constructs a `Fraction` from an integer.
61    ///
62    /// Equivalent to `Fraction::new(value,1)`.
63    pub fn from_integer(value: u32) -> Self {
64        Self(Ratio::from_integer(value))
65    }
66
67    /// Returns the reciprocal of the fraction
68    pub fn recip(self) -> Self {
69        Self(self.0.recip())
70    }
71
72    /// Checked `Fraction` × `Fraction` = `Fraction`
73    ///
74    /// Returns [`None`] for any errors
75    ///
76    /// # Examples
77    ///
78    /// ```rust
79    /// # use embedded_time::{fraction::Fraction, ConversionError};
80    /// #
81    /// assert_eq!(Fraction::new(1000, 1).checked_mul(&Fraction::new(5,5)),
82    ///     Some(Fraction::new(5_000, 5)));
83    ///
84    /// assert_eq!(Fraction::new(u32::MAX, 1).checked_mul(&Fraction::new(2,1)),
85    ///     None);
86    /// ```
87    pub fn checked_mul(&self, v: &Self) -> Option<Self> {
88        self.0.checked_mul(&v.0).map(Self)
89    }
90
91    /// Checked `Fraction` / `Fraction` = `Fraction`
92    ///
93    /// Returns [`None`] for any errors
94    ///
95    /// # Examples
96    ///
97    /// ```rust
98    /// # use embedded_time::{fraction::Fraction, ConversionError};
99    /// #
100    /// assert_eq!(Fraction::new(1000, 1).checked_div(&Fraction::new(10, 1000)),
101    ///     Some(Fraction::new(1_000_000, 10)));
102    ///
103    /// assert_eq!(Fraction::new(1, u32::MAX).checked_div(&Fraction::new(2,1)),
104    ///     None);
105    /// ```
106    pub fn checked_div(&self, v: &Self) -> Option<Self> {
107        self.0.checked_div(&v.0).map(Self)
108    }
109}
110
111impl ops::Mul<Fraction> for u32 {
112    type Output = Self;
113
114    /// Panicky u32 × `Fraction` = u32
115    fn mul(self, rhs: Fraction) -> Self::Output {
116        (rhs.0 * self).to_integer()
117    }
118}
119
120impl ops::Div<Fraction> for u32 {
121    type Output = Self;
122
123    /// Panicky u32 / `Fraction` = u32
124    #[allow(clippy::suspicious_arithmetic_impl)]
125    fn div(self, rhs: Fraction) -> Self::Output {
126        (rhs.0.recip() * self).to_integer()
127    }
128}
129
130impl ops::Mul<Fraction> for u64 {
131    type Output = Self;
132
133    /// Panicky u64 × `Fraction` = u64
134    fn mul(self, rhs: Fraction) -> Self::Output {
135        (Ratio::new_raw((*rhs.numerator()).into(), (*rhs.denominator()).into()) * self).to_integer()
136    }
137}
138
139impl ops::Div<Fraction> for u64 {
140    type Output = Self;
141
142    /// Panicky u64 / `Fraction` = u64
143    #[allow(clippy::suspicious_arithmetic_impl)]
144    fn div(self, rhs: Fraction) -> Self::Output {
145        (Ratio::new_raw((*rhs.denominator()).into(), (*rhs.numerator()).into()) * self).to_integer()
146    }
147}
148
149impl ops::Mul for Fraction {
150    type Output = Self;
151
152    /// Panicky `Fraction` × `Fraction` = `Fraction`
153    ///
154    /// # Panics
155    ///
156    /// The same reason the integer operation would panic. Namely, if the
157    /// result overflows the type.
158    fn mul(self, rhs: Self) -> Self::Output {
159        Self(self.0 * rhs.0)
160    }
161}
162
163impl ops::Div for Fraction {
164    type Output = Self;
165
166    /// Panicky `Fraction` / `Fraction` = `Fraction`
167    ///
168    /// # Panics
169    ///
170    /// The same reason the integer operation would panic. Namely, if the
171    /// result overflows the type.
172    fn div(self, rhs: Self) -> Self::Output {
173        Self(self.0 / rhs.0)
174    }
175}
176
177impl Default for Fraction {
178    fn default() -> Self {
179        Self::new(1, 1)
180    }
181}
182
183#[cfg(test)]
184mod tests {}