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}