msgpackin_core/
num.rs

1//! Abstractions for working with numbers in MessagePack Rust
2
3/// Indicates a type to which a MessagePack Rust Num instance can be coverted
4pub trait NumTo<T> {
5    /// Will this Num instance fit in the target type?
6    /// Fits is defined as lossless conversion.
7    /// E.g. an `f64` value of `42.0` will fit in all rust data types,
8    /// but a `u16` value of `256` will not fit in a `u8`
9    fn fits(&self) -> bool;
10
11    /// Convert this Num instance into the destination type.
12    /// If this instance is outside the bounds of the target
13    /// type, it will be clamped to fit. Check `fits()` first
14    /// if this is not desireable
15    fn to(&self) -> T;
16}
17
18/// A number type that encapsulates what integers and floats can
19/// be represented in MessagePack Rust
20#[derive(Clone, Copy)]
21pub enum Num {
22    /// Num is backed by f32 storage.
23    F32(f32),
24
25    /// Num is backed by f64 storage.
26    F64(f64),
27
28    /// Num is backed by i64 storage.
29    Signed(i64),
30
31    /// Num is backed by u64 storage.
32    Unsigned(u64),
33}
34
35impl core::fmt::Debug for Num {
36    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
37        match self {
38            Num::F32(n) => n.fmt(f),
39            Num::F64(n) => n.fmt(f),
40            Num::Signed(n) => n.fmt(f),
41            Num::Unsigned(n) => n.fmt(f),
42        }
43    }
44}
45
46impl core::fmt::Display for Num {
47    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
48        match self {
49            Num::F32(n) => n.fmt(f),
50            Num::F64(n) => n.fmt(f),
51            Num::Signed(n) => n.fmt(f),
52            Num::Unsigned(n) => n.fmt(f),
53        }
54    }
55}
56
57impl PartialEq for Num {
58    fn eq(&self, oth: &Num) -> bool {
59        match self {
60            Num::F32(f) => oth.eq(f),
61            Num::F64(f) => oth.eq(f),
62            Num::Signed(i) => oth.eq(i),
63            Num::Unsigned(u) => oth.eq(u),
64        }
65    }
66}
67
68macro_rules! p_eq {
69    ($($t:ty)*) => {$(
70        impl PartialEq<$t> for Num {
71            fn eq(&self, oth: &$t) -> bool {
72                match self {
73                    Num::F32(f) => *oth as f32 == *f && *oth as f32 as $t == *oth,
74                    Num::F64(f) => *oth as f64 == *f && *oth as f64 as $t == *oth,
75                    Num::Signed(i) => *oth as i64 == *i && *oth as i64 as $t == *oth,
76                    Num::Unsigned(u) => *oth as u64 == *u && *oth as u64 as $t == *oth,
77                }
78            }
79        }
80    )*};
81}
82
83p_eq!( u8 u16 u32 u64 i128 usize i8 i16 i32 i64 u128 isize f32 f64 );
84
85impl Num {
86    /// Will this Num instance fit in the target type?
87    /// Fits is defined as lossless conversion.
88    /// E.g. an `f64` value of `42.0` will fit in all rust data types,
89    /// but a `u16` value of `256` will not fit in a `u8`
90    pub fn fits<T>(&self) -> bool
91    where
92        Self: NumTo<T>,
93    {
94        NumTo::fits(self)
95    }
96
97    /// Convert this Num instance into the destination type.
98    /// If this instance is outside the bounds of the target
99    /// type, it will be clamped to fit. Check `fits()` first
100    /// if this is not desireable
101    pub fn to<T>(&self) -> T
102    where
103        Self: NumTo<T>,
104    {
105        NumTo::to(self)
106    }
107}
108
109impl From<f32> for Num {
110    fn from(t: f32) -> Self {
111        if t >= 0.0 && t as u64 as f32 == t {
112            Num::Unsigned(t as u64)
113        } else if t as i64 as f32 == t {
114            Num::Signed(t as i64)
115        } else {
116            Num::F32(t)
117        }
118    }
119}
120
121impl From<f64> for Num {
122    fn from(t: f64) -> Self {
123        if t >= 0.0 && t as u64 as f64 == t {
124            Num::Unsigned(t as u64)
125        } else if t as i64 as f64 == t {
126            Num::Signed(t as i64)
127        } else if t as f32 as f64 == t {
128            Num::F32(t as f32)
129        } else {
130            Num::F64(t)
131        }
132    }
133}
134
135macro_rules! into_num {
136    ($i:ident:$e:expr => $($t:ty)*) => {$(
137        impl From<$t> for Num {
138            fn from($i: $t) -> Self {
139                $e
140            }
141        }
142    )*};
143}
144
145into_num!(t:Num::Signed(t as i64) => i8 i16 i32 i64 isize); // NOT i128
146into_num!(t:Num::Unsigned(t as u64) => u8 u16 u32 u64 usize); // NOT u128
147
148macro_rules! num_to {
149    ($($t:ty)*) => {$(
150        impl NumTo<$t> for Num {
151            fn fits(&self) -> bool {
152                match self {
153                    Num::F32(f) => *f as $t as f32 == *f,
154                    Num::F64(f) => *f as $t as f64 == *f,
155                    Num::Signed(i) => *i as $t as i64 == *i,
156                    Num::Unsigned(u) => *u as $t as u64 == *u,
157                }
158            }
159
160            fn to(&self) -> $t {
161                match &self {
162                    Num::F32(f) => (*f) as $t,
163                    Num::F64(f) => (*f) as $t,
164                    Num::Signed(i) => (*i as i128).clamp(
165                        <$t>::MIN as i128,
166                        <$t>::MAX as i128,
167                    ) as $t,
168                    Num::Unsigned(u) => (*u as i128).clamp(
169                        <$t>::MIN as i128,
170                        <$t>::MAX as i128,
171                    ) as $t,
172                }
173            }
174        }
175    )*};
176}
177
178num_to!(u8 u16 u32 u64 usize i8 i16 i32 i64 i128 isize f32 f64);
179
180// have to hand-code this one because it overflows our i128
181impl NumTo<u128> for Num {
182    fn fits(&self) -> bool {
183        match self {
184            Num::F32(f) => *f as u128 as f32 == *f,
185            Num::F64(f) => *f as u128 as f64 == *f,
186            Num::Signed(i) => *i as u128 as i64 == *i,
187            Num::Unsigned(u) => *u as u128 as u64 == *u,
188        }
189    }
190
191    fn to(&self) -> u128 {
192        match &self {
193            Num::F32(f) => (*f) as u128,
194            Num::F64(f) => (*f) as u128,
195            Num::Signed(i) => (*i).clamp(0, i64::MAX) as u128,
196            Num::Unsigned(u) => (*u) as u128,
197        }
198    }
199}
200
201#[cfg(test)]
202mod num_tests {
203    use super::*;
204
205    #[test]
206    fn test_to() {
207        macro_rules! test_to_from {
208            ($tt:ty:$($tf:ty)*) => {$({
209                let _n: $tt = Num::from(<$tf>::MIN).to();
210                let _n: $tt = Num::from(<$tf>::MAX).to();
211            })*};
212        }
213
214        macro_rules! test_to {
215            ($($t:ty)*) => {$(
216                test_to_from!($t: u8 u16 u32 u64 usize i8 i16 i32 i64 isize f32 f64);
217            )*};
218        }
219
220        test_to!(u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize f32 f64);
221    }
222}