floating_cat/
lib.rs

1use num::traits::Zero;
2
3pub trait Category {
4    /// Type returned from `destructure`.
5    /// Should probably be CatFloat
6    type D;
7    /// Splits an f64 into its Integer and Fractional parts.
8    ///
9    /// # Examples:
10    /// ```rust
11    /// # use floating_cat::*;
12    /// let n: f64 = 1.5;
13    /// assert_eq!(n.category(), CatFloat::IntegerAndFractionalPart(1.0, 0.5));
14    /// ```
15    fn category(&self) -> Self::D;
16}
17
18#[derive(Debug, PartialEq, Copy, Clone)]
19pub enum CatFloat {
20    /// For `f64`s like `1.0`, `-100.0`, with no fractional part.
21    /// Being integer-like means this can usually be casted as an integer without issue.
22    /// Note: `f64::MAX > u128::MAX`
23    IntegerLike(f64),
24
25    /// For `f64`s like `0.5` or `-0.002`, where there's no integer part.
26    /// Casting this as an integer wouldn't be recommended, since you'd lose information.
27    FractionLike(f64),
28
29    /// The Integer and Fractional parts of an f64, in that order.
30    IntegerAndFractionalPart(f64, f64),
31
32    /// The Float was NaN
33    Nan,
34
35    /// The Float was Infinity
36    Infinity,
37}
38
39impl CatFloat {
40    /// Returns `true` if the Classified float is [`IntegerLike`].
41    ///
42    /// [`IntegerLike`]: CatFloat::IntegerLike    
43    pub fn is_integer_like(&self) -> bool {
44        matches!(self, Self::IntegerLike(..))
45    }
46
47    /// Returns `true` if the Classified float is [`FractionLike`].
48    ///
49    /// [`FractionLike`]: CatFloat::FractionLike    
50    pub fn is_fraction_like(&self) -> bool {
51        matches!(self, Self::FractionLike(..))
52    }
53
54    /// Returns `true` if the Classified float is [`IntegerAndFractionalPart`].
55    ///
56    /// [`IntegerAndFractionalPart`]: CatFloat::IntegerAndFractionalPart    
57    pub fn is_integer_and_fractional_part(&self) -> bool {
58        matches!(self, Self::IntegerAndFractionalPart(..))
59    }
60
61    /// Returns `true` if the Classified float is [`Infinity`].
62    ///
63    /// [`Infinity`]: CatFloat::Infinity    
64    pub fn is_infinity(&self) -> bool {
65        matches!(self, Self::Infinity)
66    }
67
68    /// Returns `true` if the Classified float is [`Nan`].
69    ///
70    /// [`Nan`]: CatFloat::Nan    
71    pub fn is_nan(&self) -> bool {
72        matches!(self, Self::Nan)
73    }
74}
75
76impl Category for f64 {
77    type D = CatFloat;
78    fn category(&self) -> Self::D {
79        if self.is_infinite() {
80            return CatFloat::Infinity;
81        }
82        if self.is_nan() {
83            return CatFloat::Nan;
84        }
85
86        let int_part: f64 = self.trunc();
87        let fract_part: f64 = self.fract();
88
89        if fract_part.is_zero() {
90            CatFloat::IntegerLike(int_part)
91        } else if int_part.is_zero() {
92            CatFloat::FractionLike(fract_part)
93        } else {
94            CatFloat::IntegerAndFractionalPart(int_part, fract_part)
95        }
96    }
97}
98
99#[test]
100fn trait_works() {
101    use crate::*;
102
103    let f: f64 = 1.5;
104    assert_eq!(f.category(), CatFloat::IntegerAndFractionalPart(1.0, 0.5));
105
106    let f: f64 = 1.0;
107    assert_eq!(f.category(), CatFloat::IntegerLike(1.0));
108
109    let f: f64 = 0.2;
110    assert_eq!(f.category(), CatFloat::FractionLike(0.2));
111
112    let f: f64 = f64::INFINITY;
113    assert_eq!(f.category(), CatFloat::Infinity,);
114
115    let f: f64 = f64::NAN;
116    assert_eq!(f.category(), CatFloat::Nan);
117}