Skip to main content

fix/
util.rs

1use paste::paste;
2#[cfg(feature = "typed-floats")]
3use typed_floats::StrictlyPositiveFinite;
4
5use crate::muldiv::MulDiv;
6use crate::num_traits::ConstZero;
7use crate::typenum::{Integer, NInt, NonZero, Unsigned, U10};
8use crate::Fix;
9
10/// Domain specific extensions to the `Fix` type as it's used in this project.
11pub trait FixExt: Sized {
12    /// This precision's equivalent of 1.
13    const ONE: Self;
14}
15
16macro_rules! impl_fix_ext {
17    ($bits:ident) => {
18        paste! {
19            impl<U> FixExt for Fix<$bits, U10, NInt<U>>
20            where
21                U: Unsigned + NonZero,
22            {
23                const ONE: Self =
24                    Fix::constant((10 as $bits).pow(U::U32));
25            }
26        }
27    };
28}
29
30impl_fix_ext!(u8);
31impl_fix_ext!(u16);
32impl_fix_ext!(u32);
33impl_fix_ext!(u64);
34impl_fix_ext!(u128);
35impl_fix_ext!(usize);
36impl_fix_ext!(i8);
37impl_fix_ext!(i16);
38impl_fix_ext!(i32);
39impl_fix_ext!(i64);
40impl_fix_ext!(i128);
41impl_fix_ext!(isize);
42
43impl<Bits, Base, Exp> Fix<Bits, Base, Exp>
44where
45    Self: FixExt,
46{
47    /// This precision's equivalent of 1.
48    #[must_use]
49    pub const fn one() -> Self {
50        <Self as FixExt>::ONE
51    }
52}
53
54macro_rules! impl_to_f64 {
55    ($bits:ident) => {
56        impl<Exp: Integer> Fix<$bits, U10, Exp> {
57            /// Approximate `f64` value of this fixed-point number.
58            ///
59            /// Precision loss above 2^53 bits; intended for offchain
60            /// analytics, never for onchain math.
61            ///
62            /// ```
63            /// use fix::prelude::*;
64            /// let x = UFix64::<N6>::new(1_500_000u64);
65            /// assert!((x.to_f64() - 1.5).abs() < f64::EPSILON);
66            /// ```
67            #[must_use]
68            #[allow(clippy::cast_precision_loss)]
69            pub fn to_f64(self) -> f64 {
70                self.bits as f64 * 10f64.powi(Exp::to_i32())
71            }
72        }
73    };
74}
75
76impl_to_f64!(u64);
77impl_to_f64!(i64);
78
79#[cfg(feature = "typed-floats")]
80impl<Exp: Integer> Fix<u64, U10, Exp> {
81    /// Strictly positive finite `f64` view; `None` when zero.
82    #[must_use]
83    pub fn to_positive_f64(self) -> Option<StrictlyPositiveFinite> {
84        StrictlyPositiveFinite::try_from(self.to_f64()).ok()
85    }
86}
87
88impl<Bits, Exp> Fix<Bits, U10, Exp>
89where
90    Self: FixExt,
91    Bits: MulDiv<Output = Bits>,
92{
93    /// Converts to another _Exp_, returning `None` on overflow.
94    ///
95    /// ```
96    /// use fix::prelude::*;
97    /// let source = UFix64::<N3>::new(5u64);
98    /// let target = source.checked_convert::<N6>();
99    /// assert_eq!(target, Some(UFix64::<N6>::new(5_000u64)));
100    /// ```
101    pub fn checked_convert<ToExp>(self) -> Option<Fix<Bits, U10, ToExp>>
102    where
103        Fix<Bits, U10, ToExp>: FixExt,
104    {
105        let target_one = Fix::<Bits, U10, ToExp>::one();
106        let source_one = Self::one();
107        target_one.mul_div_floor(self, source_one)
108    }
109
110    /// Converts to another _Exp_ rounding up, returning `None` on overflow.
111    ///
112    /// ```
113    /// use fix::prelude::*;
114    /// let source = UFix64::<N6>::new(5_001u64);
115    /// let target = source.checked_convert_ceil::<N3>();
116    /// assert_eq!(target, Some(UFix64::<N3>::new(6u64)));
117    /// ```
118    pub fn checked_convert_ceil<ToExp>(self) -> Option<Fix<Bits, U10, ToExp>>
119    where
120        Fix<Bits, U10, ToExp>: FixExt,
121    {
122        let target_one = Fix::<Bits, U10, ToExp>::one();
123        let source_one = Self::one();
124        target_one.mul_div_ceil(self, source_one)
125    }
126
127    /// Divides by `rhs` at the same precision, rounding down.
128    /// `None` on overflow or division by zero.
129    ///
130    /// ```
131    /// use fix::prelude::*;
132    /// let a = UFix64::<N3>::new(10_000u64);
133    /// let b = UFix64::<N3>::new(3_000u64);
134    /// assert_eq!(a.div_floor(b), Some(UFix64::<N3>::new(3_333u64)));
135    /// ```
136    pub fn div_floor(self, rhs: Self) -> Option<Self>
137    where
138        Bits: ConstZero + PartialEq,
139    {
140        if rhs == Self::zero() {
141            None
142        } else {
143            self.mul_div_floor(Self::one(), rhs)
144        }
145    }
146
147    /// Divides by `rhs` at the same precision, rounding up.
148    /// `None` on overflow or division by zero.
149    ///
150    /// ```
151    /// use fix::prelude::*;
152    /// let a = UFix64::<N3>::new(10_000u64);
153    /// let b = UFix64::<N3>::new(3_000u64);
154    /// assert_eq!(a.div_ceil(b), Some(UFix64::<N3>::new(3_334u64)));
155    /// ```
156    pub fn div_ceil(self, rhs: Self) -> Option<Self>
157    where
158        Bits: ConstZero + PartialEq,
159    {
160        if rhs == Self::zero() {
161            None
162        } else {
163            self.mul_div_ceil(Self::one(), rhs)
164        }
165    }
166
167    /// Multiplies by `rhs` at the same precision, rounding down.
168    /// `None` on overflow.
169    ///
170    /// ```
171    /// use fix::prelude::*;
172    /// let a = UFix64::<N3>::new(1_001u64);
173    /// assert_eq!(a.mul_floor(a), Some(UFix64::<N3>::new(1_002u64)));
174    /// ```
175    pub fn mul_floor(self, rhs: Self) -> Option<Self> {
176        self.mul_div_floor(rhs, Self::one())
177    }
178
179    /// Multiplies by `rhs` at the same precision, rounding up.
180    /// `None` on overflow.
181    ///
182    /// ```
183    /// use fix::prelude::*;
184    /// let a = UFix64::<N3>::new(1_001u64);
185    /// assert_eq!(a.mul_ceil(a), Some(UFix64::<N3>::new(1_003u64)));
186    /// ```
187    pub fn mul_ceil(self, rhs: Self) -> Option<Self> {
188        self.mul_div_ceil(rhs, Self::one())
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use crate::aliases::decimal::{IFix64, UFix64};
195    #[cfg(feature = "typed-floats")]
196    use crate::typenum::N6;
197    use crate::typenum::{N3, N9};
198
199    #[test]
200    fn to_f64_small_bits_exact() {
201        let x = UFix64::<N3>::new(1_500u64);
202        assert!((x.to_f64() - 1.5).abs() < f64::EPSILON);
203    }
204
205    #[test]
206    fn to_f64_negative_bits_and_exp() {
207        let x = IFix64::<N9>::new(-975i64);
208        assert!((x.to_f64() - -9.75e-7).abs() < 1e-21);
209    }
210
211    #[test]
212    #[allow(clippy::excessive_precision)]
213    fn to_f64_max_bits_relative_error() {
214        let got = UFix64::<N9>::new(u64::MAX).to_f64();
215        let expected = 18_446_744_073.709_551_615_f64;
216        assert!(((got - expected) / expected).abs() < 1e-15);
217    }
218
219    #[cfg(feature = "typed-floats")]
220    #[test]
221    fn to_positive_f64_zero_is_none() {
222        assert!(UFix64::<N6>::zero().to_positive_f64().is_none());
223    }
224
225    #[cfg(feature = "typed-floats")]
226    #[test]
227    fn to_positive_f64_nonzero_is_some() {
228        let x = UFix64::<N6>::new(2_500_000u64);
229        let positive = x.to_positive_f64().map(f64::from);
230        assert_eq!(positive, Some(x.to_f64()));
231    }
232
233    #[test]
234    fn div_floor_rounds_down() {
235        let a = UFix64::<N3>::new(10_000u64);
236        let b = UFix64::<N3>::new(3_000u64);
237        assert_eq!(a.div_floor(b), Some(UFix64::<N3>::new(3_333u64)));
238    }
239
240    #[test]
241    fn div_ceil_rounds_up() {
242        let a = UFix64::<N3>::new(10_000u64);
243        let b = UFix64::<N3>::new(3_000u64);
244        assert_eq!(a.div_ceil(b), Some(UFix64::<N3>::new(3_334u64)));
245    }
246
247    #[test]
248    fn div_exact_floor_eq_ceil() {
249        let a = UFix64::<N3>::new(9_000u64);
250        let b = UFix64::<N3>::new(3_000u64);
251        let exact = Some(UFix64::<N3>::new(3_000u64));
252        assert_eq!(a.div_floor(b), exact);
253        assert_eq!(a.div_ceil(b), exact);
254    }
255
256    #[test]
257    fn div_by_zero_is_none() {
258        let a = UFix64::<N3>::new(10_000u64);
259        assert_eq!(a.div_floor(UFix64::<N3>::zero()), None);
260        assert_eq!(a.div_ceil(UFix64::<N3>::zero()), None);
261    }
262
263    #[test]
264    fn div_negative_rounds_toward_neg_infinity() {
265        let a = IFix64::<N3>::new(-10_000i64);
266        let b = IFix64::<N3>::new(3_000i64);
267        assert_eq!(a.div_floor(b), Some(IFix64::<N3>::new(-3_334i64)));
268        assert_eq!(a.div_ceil(b), Some(IFix64::<N3>::new(-3_333i64)));
269    }
270
271    #[test]
272    fn mul_floor_rounds_down() {
273        let a = UFix64::<N3>::new(1_001u64);
274        assert_eq!(a.mul_floor(a), Some(UFix64::<N3>::new(1_002u64)));
275    }
276
277    #[test]
278    fn mul_floor_exact() {
279        let a = UFix64::<N3>::new(2_000u64);
280        let b = UFix64::<N3>::new(1_500u64);
281        assert_eq!(a.mul_floor(b), Some(UFix64::<N3>::new(3_000u64)));
282    }
283
284    #[test]
285    fn mul_floor_overflow_is_none() {
286        let a = UFix64::<N3>::new(u64::MAX);
287        assert_eq!(a.mul_floor(a), None);
288    }
289
290    #[test]
291    fn mul_ceil_rounds_up() {
292        let a = UFix64::<N3>::new(1_001u64);
293        assert_eq!(a.mul_ceil(a), Some(UFix64::<N3>::new(1_003u64)));
294    }
295
296    #[test]
297    fn mul_ceil_exact() {
298        let a = UFix64::<N3>::new(2_000u64);
299        let b = UFix64::<N3>::new(1_500u64);
300        assert_eq!(a.mul_ceil(b), Some(UFix64::<N3>::new(3_000u64)));
301    }
302
303    #[test]
304    fn mul_ceil_overflow_is_none() {
305        let a = UFix64::<N3>::new(u64::MAX);
306        assert_eq!(a.mul_ceil(a), None);
307    }
308}