1use paste::paste;
2#[cfg(feature = "typed-floats")]
3use typed_floats::StrictlyPositiveFinite;
4
5use crate::muldiv::MulDiv;
6use crate::num_traits::{float::FloatCore, 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 let exp = Exp::to_i32();
71 let scale = FloatCore::powi(10f64, exp.saturating_abs());
72 if exp.is_negative() {
73 self.bits as f64 / scale
74 } else {
75 self.bits as f64 * scale
76 }
77 }
78 }
79 };
80}
81
82impl_to_f64!(u64);
83impl_to_f64!(i64);
84
85#[cfg(feature = "typed-floats")]
86impl<Exp: Integer> Fix<u64, U10, Exp> {
87 #[must_use]
89 pub fn to_positive_f64(self) -> Option<StrictlyPositiveFinite> {
90 StrictlyPositiveFinite::try_from(self.to_f64()).ok()
91 }
92}
93
94impl<Bits, Exp> Fix<Bits, U10, Exp>
95where
96 Self: FixExt,
97 Bits: MulDiv<Output = Bits>,
98{
99 pub fn checked_convert<ToExp>(self) -> Option<Fix<Bits, U10, ToExp>>
108 where
109 Fix<Bits, U10, ToExp>: FixExt,
110 {
111 let target_one = Fix::<Bits, U10, ToExp>::one();
112 let source_one = Self::one();
113 target_one.mul_div_floor(self, source_one)
114 }
115
116 pub fn checked_convert_ceil<ToExp>(self) -> Option<Fix<Bits, U10, ToExp>>
125 where
126 Fix<Bits, U10, ToExp>: FixExt,
127 {
128 let target_one = Fix::<Bits, U10, ToExp>::one();
129 let source_one = Self::one();
130 target_one.mul_div_ceil(self, source_one)
131 }
132
133 pub fn div_floor(self, rhs: Self) -> Option<Self>
143 where
144 Bits: ConstZero + PartialEq,
145 {
146 if rhs == Self::zero() {
147 None
148 } else {
149 self.mul_div_floor(Self::one(), rhs)
150 }
151 }
152
153 pub fn div_ceil(self, rhs: Self) -> Option<Self>
163 where
164 Bits: ConstZero + PartialEq,
165 {
166 if rhs == Self::zero() {
167 None
168 } else {
169 self.mul_div_ceil(Self::one(), rhs)
170 }
171 }
172
173 pub fn mul_floor(self, rhs: Self) -> Option<Self> {
182 self.mul_div_floor(rhs, Self::one())
183 }
184
185 pub fn mul_ceil(self, rhs: Self) -> Option<Self> {
194 self.mul_div_ceil(rhs, Self::one())
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use crate::aliases::decimal::{IFix64, UFix64};
201 #[cfg(feature = "typed-floats")]
202 use crate::typenum::N6;
203 use crate::typenum::{N3, N9};
204
205 #[test]
206 fn to_f64_small_bits_exact() {
207 let x = UFix64::<N3>::new(1_500u64);
208 assert!((x.to_f64() - 1.5).abs() < f64::EPSILON);
209 }
210
211 #[test]
212 fn to_f64_negative_bits_and_exp() {
213 let x = IFix64::<N9>::new(-975i64);
214 assert!((x.to_f64() - -9.75e-7).abs() < 1e-21);
215 }
216
217 #[test]
218 #[allow(clippy::excessive_precision)]
219 fn to_f64_max_bits_relative_error() {
220 let got = UFix64::<N9>::new(u64::MAX).to_f64();
221 let expected = 18_446_744_073.709_551_615_f64;
222 assert!(((got - expected) / expected).abs() < 1e-15);
223 }
224
225 #[cfg(feature = "typed-floats")]
226 #[test]
227 fn to_positive_f64_zero_is_none() {
228 assert!(UFix64::<N6>::zero().to_positive_f64().is_none());
229 }
230
231 #[cfg(feature = "typed-floats")]
232 #[test]
233 fn to_positive_f64_nonzero_is_some() {
234 let x = UFix64::<N6>::new(2_500_000u64);
235 let positive = x.to_positive_f64().map(f64::from);
236 assert_eq!(positive, Some(x.to_f64()));
237 }
238
239 #[test]
240 fn div_floor_rounds_down() {
241 let a = UFix64::<N3>::new(10_000u64);
242 let b = UFix64::<N3>::new(3_000u64);
243 assert_eq!(a.div_floor(b), Some(UFix64::<N3>::new(3_333u64)));
244 }
245
246 #[test]
247 fn div_ceil_rounds_up() {
248 let a = UFix64::<N3>::new(10_000u64);
249 let b = UFix64::<N3>::new(3_000u64);
250 assert_eq!(a.div_ceil(b), Some(UFix64::<N3>::new(3_334u64)));
251 }
252
253 #[test]
254 fn div_exact_floor_eq_ceil() {
255 let a = UFix64::<N3>::new(9_000u64);
256 let b = UFix64::<N3>::new(3_000u64);
257 let exact = Some(UFix64::<N3>::new(3_000u64));
258 assert_eq!(a.div_floor(b), exact);
259 assert_eq!(a.div_ceil(b), exact);
260 }
261
262 #[test]
263 fn div_by_zero_is_none() {
264 let a = UFix64::<N3>::new(10_000u64);
265 assert_eq!(a.div_floor(UFix64::<N3>::zero()), None);
266 assert_eq!(a.div_ceil(UFix64::<N3>::zero()), None);
267 }
268
269 #[test]
270 fn div_negative_rounds_toward_neg_infinity() {
271 let a = IFix64::<N3>::new(-10_000i64);
272 let b = IFix64::<N3>::new(3_000i64);
273 assert_eq!(a.div_floor(b), Some(IFix64::<N3>::new(-3_334i64)));
274 assert_eq!(a.div_ceil(b), Some(IFix64::<N3>::new(-3_333i64)));
275 }
276
277 #[test]
278 fn mul_floor_rounds_down() {
279 let a = UFix64::<N3>::new(1_001u64);
280 assert_eq!(a.mul_floor(a), Some(UFix64::<N3>::new(1_002u64)));
281 }
282
283 #[test]
284 fn mul_floor_exact() {
285 let a = UFix64::<N3>::new(2_000u64);
286 let b = UFix64::<N3>::new(1_500u64);
287 assert_eq!(a.mul_floor(b), Some(UFix64::<N3>::new(3_000u64)));
288 }
289
290 #[test]
291 fn mul_floor_overflow_is_none() {
292 let a = UFix64::<N3>::new(u64::MAX);
293 assert_eq!(a.mul_floor(a), None);
294 }
295
296 #[test]
297 fn mul_ceil_rounds_up() {
298 let a = UFix64::<N3>::new(1_001u64);
299 assert_eq!(a.mul_ceil(a), Some(UFix64::<N3>::new(1_003u64)));
300 }
301
302 #[test]
303 fn mul_ceil_exact() {
304 let a = UFix64::<N3>::new(2_000u64);
305 let b = UFix64::<N3>::new(1_500u64);
306 assert_eq!(a.mul_ceil(b), Some(UFix64::<N3>::new(3_000u64)));
307 }
308
309 #[test]
310 fn mul_ceil_overflow_is_none() {
311 let a = UFix64::<N3>::new(u64::MAX);
312 assert_eq!(a.mul_ceil(a), None);
313 }
314}