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
10pub trait FixExt: Sized {
12 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 #[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 #[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 #[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 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 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 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 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 pub fn mul_floor(self, rhs: Self) -> Option<Self> {
176 self.mul_div_floor(rhs, Self::one())
177 }
178
179 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}