fix_float/
ffx.rs

1use std::cmp::Ordering;
2use std::fmt::{Debug, Display, Formatter};
3use std::hash::{Hash, Hasher};
4use std::num::FpCategory::{Infinite, Nan};
5use std::ops::Deref;
6
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10/// Error type when try_from is invoked
11#[derive(Debug, PartialEq, Eq)]
12pub enum ErrorTryFrom {
13    /// Happens when the value given is Nan (any type of Nan)
14    CannotFixNan,
15    /// Happens when the value given is Infinity (any type of Infinity)
16    CannotFixInfinity,
17}
18
19macro_rules! _impl_ty {
20    ($base:ty, $new:ident) => {
21        doc_comment! {
22            concat!(
23        "Abstract wrapper for fix ", stringify!($base), ", or ", stringify!($new), " for short.
24
25This wrapper implements:
26 - Default
27 - Copy
28 - Clone
29 - Debug
30 - Display
31 - PartialEq
32 - Eq
33 - PartialOrd
34 - Ord
35 - Hash
36 - Deref<Target = ", stringify!($base), ">
37 - TryFrom", "<", stringify!($base), ">
38
39```
40# use fix_float::*;
41# fn main() {
42	let x: ", stringify!($base), " =  42.42;
43	let a: ", stringify!($new), " = ", stringify!($new), "::try_from(x).unwrap();
44	let b: ", stringify!($new), " = x.try_into().unwrap();
45	let c: ", stringify!($new), " = ", stringify!($new), "!(x);
46# }
47```
48",
49            ),
50
51            #[allow(non_camel_case_types)]
52            #[derive(Default, Clone, Copy)]
53			#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
54            pub struct $new {
55                x: $base,
56            }
57
58            impl $new {
59                #[inline]
60                fn is_fixable(x: $base) -> Option<ErrorTryFrom> {
61                    match x.classify() {
62                        Nan => Some(ErrorTryFrom::CannotFixNan),
63                        Infinite => Some(ErrorTryFrom::CannotFixInfinity),
64                        _ => None,
65                    }
66                }
67
68                #[inline]
69                fn try_from(x: $base) -> Result<$new, ErrorTryFrom> {
70                    match Self::is_fixable(x) {
71                        Some(err) => Err(err),
72                        None => Ok($new {
73                            x: if (x == (0.0 as $base)) {
74                                0.0 as $base
75                            } else {
76                                x
77                            },
78                        }),
79                    }
80                }
81
82                #[inline]
83                fn my_partial_cmp(lhs: &$new, rhs: &$new) -> Option<Ordering> {
84                    Some(lhs.x.total_cmp(&rhs.x))
85                }
86
87                #[inline]
88                fn my_cmp(lhs: &$new, rhs: &$new) -> Ordering {
89                    lhs.x.total_cmp(&rhs.x)
90                }
91
92                #[inline]
93                fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
94                    f.pad(&format!("fix {}", self.x))
95                }
96            }
97
98            impl PartialEq for $new {
99                #[inline]
100                #[must_use]
101                fn eq(&self, other: &Self) -> bool {
102                    self.x == other.x
103                }
104            }
105
106            impl Eq for $new {}
107
108            impl PartialOrd for $new {
109                #[inline]
110                #[must_use]
111                fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
112                    Self::my_partial_cmp(self, other)
113                }
114            }
115
116            impl Ord for $new {
117                #[inline]
118                #[must_use]
119                fn cmp(&self, other: &Self) -> Ordering {
120                    Self::my_cmp(self, other)
121                }
122            }
123
124            impl Debug for $new {
125                fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
126                    self.fmt(f)
127                }
128            }
129
130            impl Display for $new {
131                fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
132                    self.fmt(f)
133                }
134            }
135
136            impl Hash for $new {
137                fn hash<H: Hasher>(&self, state: &mut H) {
138                    self.x.to_bits().hash(state)
139                }
140            }
141
142            impl From<$new> for $base {
143                #[inline]
144                #[must_use]
145                fn from(x: $new) -> Self {
146                    x.x
147                }
148            }
149
150            impl TryFrom<$base> for $new {
151                type Error = ErrorTryFrom;
152
153                #[inline]
154                fn try_from(value: $base) -> Result<Self, Self::Error> {
155                    Self::try_from(value)
156                }
157            }
158
159            impl Deref for $new {
160                type Target = $base;
161
162                #[inline]
163                #[must_use]
164                fn deref(&self) -> &Self::Target {
165                    &self.x
166                }
167            }
168
169            doc_comment!{
170                concat!("Macro that does try_from and unwraps it.
171
172`", stringify!($new), "!(x)` <=> `fix_float::", stringify!($new), "::try_from(x).unwrap()`
173
174You should use this macro when you are sure that you are not having Nan or Infinity. Otherwise, it will panic.
175
176				"),
177                #[macro_export]
178                macro_rules! $new {
179                    ($x:literal) => {
180                        $crate::$new::try_from($x).unwrap()
181                    };
182                    ($x:expr) => {
183                        $crate::$new::try_from($x).unwrap()
184                    };
185                }
186            }
187        }
188    };
189}
190
191_impl_ty!(f64, ff64);
192_impl_ty!(f32, ff32);
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197
198    const F64_COMMON_FLOATS: &[f64] = &[
199        0.0,
200        1.0,
201        -1.0,
202        42.42,
203        f64::EPSILON,
204        f64::MIN,
205        f64::MAX,
206        std::f64::consts::E,
207        std::f64::consts::FRAC_1_PI,
208        std::f64::consts::FRAC_1_SQRT_2,
209        std::f64::consts::FRAC_2_PI,
210        std::f64::consts::FRAC_2_SQRT_PI,
211        std::f64::consts::FRAC_PI_2,
212        std::f64::consts::FRAC_PI_3,
213        std::f64::consts::FRAC_PI_4,
214        std::f64::consts::FRAC_PI_6,
215        std::f64::consts::FRAC_PI_8,
216        std::f64::consts::LN_2,
217        std::f64::consts::LN_10,
218        std::f64::consts::LOG2_10,
219        std::f64::consts::LOG2_E,
220        std::f64::consts::LOG10_2,
221        std::f64::consts::LOG10_E,
222        std::f64::consts::PI,
223        std::f64::consts::SQRT_2,
224        std::f64::consts::TAU,
225    ];
226
227    const F32_COMMON_FLOATS: &[f32] = &[
228        0.0,
229        1.0,
230        -1.0,
231        42.42,
232        f32::EPSILON,
233        f32::MIN,
234        f32::MAX,
235        std::f32::consts::E,
236        std::f32::consts::FRAC_1_PI,
237        std::f32::consts::FRAC_1_SQRT_2,
238        std::f32::consts::FRAC_2_PI,
239        std::f32::consts::FRAC_2_SQRT_PI,
240        std::f32::consts::FRAC_PI_2,
241        std::f32::consts::FRAC_PI_3,
242        std::f32::consts::FRAC_PI_4,
243        std::f32::consts::FRAC_PI_6,
244        std::f32::consts::FRAC_PI_8,
245        std::f32::consts::LN_2,
246        std::f32::consts::LN_10,
247        std::f32::consts::LOG2_10,
248        std::f32::consts::LOG2_E,
249        std::f32::consts::LOG10_2,
250        std::f32::consts::LOG10_E,
251        std::f32::consts::PI,
252        std::f32::consts::SQRT_2,
253        std::f32::consts::TAU,
254    ];
255
256    #[test]
257    fn ff64_common_floats() {
258        for &x in F64_COMMON_FLOATS {
259            let fx = ff64!(x);
260            assert_eq!(x, *fx)
261        }
262    }
263
264    #[test]
265    #[should_panic]
266    fn ff64_nan() {
267        let _ = ff64!(f64::NAN);
268    }
269
270    #[test]
271    #[should_panic]
272    fn ff64_inf() {
273        let _ = ff64!(f64::INFINITY);
274    }
275
276    #[test]
277    #[should_panic]
278    fn ff64_neg_inf() {
279        let _ = ff64!(f64::NEG_INFINITY);
280    }
281
282    #[test]
283    fn ff32_common_floats() {
284        for &x in F32_COMMON_FLOATS {
285            let fx = ff32!(x);
286            assert_eq!(x, *fx)
287        }
288    }
289
290    #[test]
291    #[should_panic]
292    fn ff32_nan() {
293        let _ = ff32!(f32::NAN);
294    }
295
296    #[test]
297    #[should_panic]
298    fn ff32_inf() {
299        let _ = ff32!(f32::INFINITY);
300    }
301
302    #[test]
303    #[should_panic]
304    fn ff32_neg_inf() {
305        let _ = ff32!(f32::NEG_INFINITY);
306    }
307
308    #[test]
309    fn default_ff64() {
310        let a = ff64::default();
311
312        assert_eq!(f64::from(a), f64::default());
313    }
314
315    #[test]
316    fn default_ff32() {
317        let a = ff32::default();
318
319        assert_eq!(f32::from(a), f32::default());
320    }
321
322    #[test]
323    fn mem_f64() {
324        assert_eq!(std::mem::size_of::<f64>(), std::mem::size_of::<ff64>())
325    }
326
327    #[test]
328    fn mem_f32() {
329        assert_eq!(std::mem::size_of::<f32>(), std::mem::size_of::<ff32>())
330    }
331
332    #[test]
333    fn eq_zero() {
334        let a = ff64!(0.0);
335        let b = ff64!(0.0);
336        let c = ff64!(-0.0);
337
338        assert_eq!(a, b);
339        assert_eq!(a, c);
340    }
341
342    #[test]
343    fn eq_fuzzy() {
344        for _ in 0..10000 {
345            let random_float = rand::random();
346            let a = ff64!(random_float);
347            let b = ff64!(random_float);
348
349            assert_eq!(a, b);
350        }
351    }
352
353    #[test]
354    fn ord_fuzzy() {
355        for _ in 0..10000 {
356            let a = rand::random();
357            let b = rand::random();
358            let fa = ff64!(a);
359            let fb = ff64!(b);
360
361            assert_eq!(a.partial_cmp(&b), fa.partial_cmp(&fb));
362            assert_eq!(a.partial_cmp(&b).unwrap(), fa.cmp(&fb));
363        }
364    }
365
366    #[test]
367    fn fmt() {
368        assert_eq!(format!("{}", ff64!(2.42)), "fix 2.42");
369        assert_eq!(format!("{:?}", ff64!(2.42)), "fix 2.42");
370        assert_eq!(format!("{:}", ff64!(2.42)), "fix 2.42");
371        assert_eq!(format!("{:10}", ff64!(2.42)), "fix 2.42  ");
372        assert_eq!(format!("{:15}", ff64!(2.42)), "fix 2.42       ");
373        assert_eq!(format!("{:0<15}", ff64!(2.42)), "fix 2.420000000");
374        assert_eq!(format!("{:>15}", ff64!(2.42)), "       fix 2.42");
375        assert_eq!(format!("{:0>15}", ff64!(2.42)), "0000000fix 2.42");
376    }
377
378    #[test]
379    fn is_send() {
380        fn assert_send<T: Send>() {}
381
382        assert_send::<ff64>();
383        assert_send::<ff32>();
384    }
385
386    #[test]
387    fn is_sync() {
388        fn assert_sync<T: Sync>() {}
389
390        assert_sync::<ff64>();
391        assert_sync::<ff32>();
392    }
393
394    #[test]
395    fn try_from() {
396        let a: ff64 = ff64::try_from(42.42).unwrap();
397        let b: ff64 = (42.42).try_into().unwrap();
398        let c: ff64 = ff64!(42.42);
399
400        assert_eq!(a, b);
401        assert_eq!(a, c);
402
403        assert_eq!(*a, *b);
404        assert_eq!(*a, *c);
405    }
406
407    #[test]
408    fn try_from_nan() {
409        let a = ff64::try_from(f64::NAN);
410        assert!(a.is_err());
411        assert_eq!(a.unwrap_err(), ErrorTryFrom::CannotFixNan);
412    }
413
414    #[test]
415    fn try_from_inf() {
416        let a = ff64::try_from(f64::INFINITY);
417        assert!(a.is_err());
418        assert_eq!(a.unwrap_err(), ErrorTryFrom::CannotFixInfinity);
419
420        let b = ff64::try_from(f64::NEG_INFINITY);
421        assert!(b.is_err());
422        assert_eq!(b.unwrap_err(), ErrorTryFrom::CannotFixInfinity);
423    }
424
425    #[test]
426    #[cfg(feature = "serde")]
427    fn serde_json() {
428        let a = ff64!(42.42);
429
430        let json_a = serde_json::to_string(&a).unwrap();
431        let a_json_a: ff64 = serde_json::from_str(&json_a).unwrap();
432
433        assert_eq!(json_a, "{\"x\":42.42}");
434        assert_eq!(a, a_json_a);
435    }
436}