embedded_time/
fixed_point.rs

1//! Fixed-point values
2use crate::{fraction::Fraction, time_int::TimeInt, ConversionError};
3use core::{convert::TryFrom, mem::size_of, prelude::v1::*};
4use num::{Bounded, CheckedDiv, CheckedMul};
5
6/// Fixed-point value type
7///
8/// QX.32 where X: bit-width of `T`
9pub trait FixedPoint: Sized + Copy {
10    /// The _integer_ (magnitude) type
11    type T: TimeInt;
12
13    /// The fractional _scaling factor_
14    const SCALING_FACTOR: Fraction;
15
16    /// Not generally useful to call directly
17    ///
18    /// It only exists to allow FixedPoint methods with default definitions to create a
19    /// new fixed-point type
20    #[doc(hidden)]
21    fn new(value: Self::T) -> Self;
22
23    /// Returns the integer part of the `FixedPoint` value
24    ///
25    /// ```rust
26    /// # use embedded_time::{ rate::*};
27    /// #
28    /// assert_eq!(Hertz(45_u32).integer(), 45_u32);
29    /// ```
30    fn integer(&self) -> Self::T;
31
32    /// Constructs a `FixedPoint` value from _integer_ and _scaling-factor_ ([`Fraction`]) parts
33    ///
34    /// # Errors
35    ///
36    /// Failure will only occur if the provided value does not fit in the selected destination type.
37    ///
38    /// - [`ConversionError::Unspecified`]
39    /// - [`ConversionError::Overflow`]
40    /// - [`ConversionError::ConversionFailure`]
41    #[doc(hidden)]
42    fn from_ticks<SourceInt: TimeInt>(
43        ticks: SourceInt,
44        scaling_factor: Fraction,
45    ) -> Result<Self, ConversionError>
46    where
47        Self::T: TryFrom<SourceInt>,
48    {
49        if size_of::<Self::T>() > size_of::<SourceInt>() {
50            // the dest integer is wider than the source, first promote the source integer to the
51            // dest type
52            let ticks = Self::T::try_from(ticks).map_err(|_| ConversionError::ConversionFailure)?;
53            let ticks =
54                Self::convert_ticks(ticks, scaling_factor).ok_or(ConversionError::Unspecified)?;
55            Ok(Self::new(ticks))
56        } else {
57            let ticks =
58                Self::convert_ticks(ticks, scaling_factor).ok_or(ConversionError::Unspecified)?;
59            let ticks = Self::T::try_from(ticks).map_err(|_| ConversionError::ConversionFailure)?;
60            Ok(Self::new(ticks))
61        }
62    }
63
64    #[doc(hidden)]
65    fn convert_ticks<T: TimeInt>(ticks: T, scaling_factor: Fraction) -> Option<T> {
66        if (scaling_factor >= Fraction::new(1, 1) && Self::SCALING_FACTOR <= Fraction::new(1, 1))
67            || (scaling_factor <= Fraction::new(1, 1)
68                && Self::SCALING_FACTOR >= Fraction::new(1, 1))
69        {
70            TimeInt::checked_div_fraction(
71                &TimeInt::checked_mul_fraction(&ticks, &scaling_factor)?,
72                &Self::SCALING_FACTOR,
73            )
74        } else {
75            TimeInt::checked_mul_fraction(
76                &ticks,
77                &scaling_factor.checked_div(&Self::SCALING_FACTOR)?,
78            )
79        }
80    }
81
82    /// Returns the _integer_ of the fixed-point value after converting to the _scaling factor_
83    /// provided
84    ///
85    /// # Examples
86    ///
87    /// ```rust
88    /// # use embedded_time::{fraction::Fraction,  rate::*};
89    /// #
90    /// assert_eq!(Hertz(2_u32).into_ticks(Fraction::new(1, 1_000)), Ok(2_000_u32));
91    /// ```
92    ///
93    /// # Errors
94    ///
95    /// Failure will only occur if the provided value does not fit in the selected destination type.
96    ///
97    /// [`ConversionError::Overflow`] : The conversion of the _scaling factor_ causes an overflow.
98    /// [`ConversionError::ConversionFailure`] : The _integer_ type cast to that of the destination
99    /// fails.
100    #[doc(hidden)]
101    fn into_ticks<T: TimeInt>(self, fraction: Fraction) -> Result<T, ConversionError>
102    where
103        Self::T: TimeInt,
104        T: TryFrom<Self::T>,
105    {
106        if size_of::<T>() > size_of::<Self::T>() {
107            let ticks =
108                T::try_from(self.integer()).map_err(|_| ConversionError::ConversionFailure)?;
109
110            if fraction > Fraction::new(1, 1) {
111                TimeInt::checked_div_fraction(
112                    &TimeInt::checked_mul_fraction(&ticks, &Self::SCALING_FACTOR)
113                        .ok_or(ConversionError::Unspecified)?,
114                    &fraction,
115                )
116                .ok_or(ConversionError::Unspecified)
117            } else {
118                TimeInt::checked_mul_fraction(
119                    &ticks,
120                    &Self::SCALING_FACTOR
121                        .checked_div(&fraction)
122                        .ok_or(ConversionError::Unspecified)?,
123                )
124                .ok_or(ConversionError::Unspecified)
125            }
126        } else {
127            let ticks = if Self::SCALING_FACTOR > Fraction::new(1, 1) {
128                TimeInt::checked_div_fraction(
129                    &TimeInt::checked_mul_fraction(&self.integer(), &Self::SCALING_FACTOR)
130                        .ok_or(ConversionError::Unspecified)?,
131                    &fraction,
132                )
133                .ok_or(ConversionError::Unspecified)?
134            } else {
135                TimeInt::checked_mul_fraction(
136                    &self.integer(),
137                    &Self::SCALING_FACTOR
138                        .checked_div(&fraction)
139                        .ok_or(ConversionError::Unspecified)?,
140                )
141                .ok_or(ConversionError::Unspecified)?
142            };
143
144            T::try_from(ticks).map_err(|_| ConversionError::ConversionFailure)
145        }
146    }
147
148    /// Panicky addition
149    #[doc(hidden)]
150    fn add<Rhs: FixedPoint>(self, rhs: Rhs) -> Self
151    where
152        Self: TryFrom<Rhs>,
153    {
154        let v = if let Ok(v) = Self::try_from(rhs) {
155            v
156        } else {
157            panic!("Add failed")
158        };
159        Self::new(self.integer() + v.integer())
160    }
161
162    /// Panicky subtraction
163    #[doc(hidden)]
164    fn sub<Rhs: FixedPoint>(self, rhs: Rhs) -> Self
165    where
166        Self: TryFrom<Rhs>,
167    {
168        let v = if let Ok(v) = Self::try_from(rhs) {
169            v
170        } else {
171            panic!("Sub failed")
172        };
173
174        Self::new(self.integer() - v.integer())
175    }
176
177    /// Panicky multiplication
178    #[doc(hidden)]
179    fn mul(self, rhs: Self::T) -> Self {
180        Self::new(self.integer() * rhs)
181    }
182
183    /// Multiply with overflow checking
184    fn checked_mul(&self, rhs: &Self::T) -> Option<Self> {
185        Some(Self::new((self.integer()).checked_mul(rhs)?))
186    }
187
188    /// Panicky division
189    #[doc(hidden)]
190    fn div(self, rhs: Self::T) -> Self {
191        Self::new(self.integer() / rhs)
192    }
193
194    /// Multiply with overflow checking
195    fn checked_div(&self, rhs: &Self::T) -> Option<Self> {
196        Some(Self::new((self.integer()).checked_div(rhs)?))
197    }
198
199    /// Panicky remainder
200    #[doc(hidden)]
201    fn rem<Rhs: FixedPoint>(self, rhs: Rhs) -> Self
202    where
203        Self: TryFrom<Rhs>,
204    {
205        match Self::try_from(rhs) {
206            Ok(rhs) => {
207                if rhs.integer() > Self::T::from(0) {
208                    Self::new(self.integer() % rhs.integer())
209                } else {
210                    Self::new(Self::T::from(0))
211                }
212            }
213            Err(_) => self,
214        }
215    }
216
217    /// Returns the minimum integer value
218    fn min_value() -> Self::T {
219        Self::T::min_value()
220    }
221
222    /// Returns the maximum integer value
223    fn max_value() -> Self::T {
224        Self::T::max_value()
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231    use crate::duration::*;
232    use crate::fixed_point;
233
234    #[test]
235    fn from_ticks() {
236        assert_eq!(
237            fixed_point::FixedPoint::from_ticks(200_u32, Fraction::new(1, 1_000)),
238            Ok(Milliseconds(200_u64))
239        );
240        assert_eq!(
241            fixed_point::FixedPoint::from_ticks(200_u32, Fraction::new(1_000, 1)),
242            Ok(Seconds(200_000_u64))
243        );
244    }
245}