ron/value/
number.rs

1use std::{
2    cmp::{Eq, Ordering},
3    hash::{Hash, Hasher},
4};
5
6use serde::{de::Visitor, Serialize, Serializer};
7
8/// A wrapper for any numeric primitive type in Rust
9#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Hash, Ord)]
10pub enum Number {
11    I8(i8),
12    I16(i16),
13    I32(i32),
14    I64(i64),
15    #[cfg(feature = "integer128")]
16    I128(i128),
17    U8(u8),
18    U16(u16),
19    U32(u32),
20    U64(u64),
21    #[cfg(feature = "integer128")]
22    U128(u128),
23    F32(F32),
24    F64(F64),
25}
26
27impl Serialize for Number {
28    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
29        match self {
30            Self::I8(v) => serializer.serialize_i8(*v),
31            Self::I16(v) => serializer.serialize_i16(*v),
32            Self::I32(v) => serializer.serialize_i32(*v),
33            Self::I64(v) => serializer.serialize_i64(*v),
34            #[cfg(feature = "integer128")]
35            Self::I128(v) => serializer.serialize_i128(*v),
36            Self::U8(v) => serializer.serialize_u8(*v),
37            Self::U16(v) => serializer.serialize_u16(*v),
38            Self::U32(v) => serializer.serialize_u32(*v),
39            Self::U64(v) => serializer.serialize_u64(*v),
40            #[cfg(feature = "integer128")]
41            Self::U128(v) => serializer.serialize_u128(*v),
42            Self::F32(v) => serializer.serialize_f32(v.get()),
43            Self::F64(v) => serializer.serialize_f64(v.get()),
44        }
45    }
46}
47
48impl Number {
49    pub fn visit<'de, V: Visitor<'de>, E: serde::de::Error>(
50        &self,
51        visitor: V,
52    ) -> Result<V::Value, E> {
53        match self {
54            Self::I8(v) => visitor.visit_i8(*v),
55            Self::I16(v) => visitor.visit_i16(*v),
56            Self::I32(v) => visitor.visit_i32(*v),
57            Self::I64(v) => visitor.visit_i64(*v),
58            #[cfg(feature = "integer128")]
59            Self::I128(v) => visitor.visit_i128(*v),
60            Self::U8(v) => visitor.visit_u8(*v),
61            Self::U16(v) => visitor.visit_u16(*v),
62            Self::U32(v) => visitor.visit_u32(*v),
63            Self::U64(v) => visitor.visit_u64(*v),
64            #[cfg(feature = "integer128")]
65            Self::U128(v) => visitor.visit_u128(*v),
66            Self::F32(v) => visitor.visit_f32(v.get()),
67            Self::F64(v) => visitor.visit_f64(v.get()),
68        }
69    }
70}
71
72macro_rules! float_ty {
73    ($ty:ident($float:ty)) => {
74        #[doc = concat!(
75                    "A wrapper for [`", stringify!($float), "`], which implements [`Eq`], ",
76                    "[`Hash`] and [`Ord`] using [`", stringify!($float), "::total_cmp`] ",
77                    "for a total order comparison",
78                )]
79        #[derive(Copy, Clone, Debug)] // GRCOV_EXCL_LINE
80        pub struct $ty(pub $float);
81
82        impl $ty {
83            #[doc = concat!("Construct a new [`", stringify!($ty), "`].")]
84            #[must_use]
85            pub fn new(v: $float) -> Self {
86                Self(v)
87            }
88
89            #[doc = concat!("Returns the wrapped [`", stringify!($float), "`].")]
90            #[must_use]
91            pub fn get(self) -> $float {
92                self.0
93            }
94        }
95
96        impl From<$float> for $ty {
97            fn from(v: $float) -> Self {
98                Self::new(v)
99            }
100        }
101
102        /// Partial equality comparison
103        ///
104        #[doc = concat!(
105                    "In order to be able to use [`", stringify!($ty), "`] as a mapping key, ",
106                    "floating values use [`", stringify!($float), "::total_cmp`] for a total ",
107                    "order comparison.",
108                )]
109        ///
110        /// See the [`Ord`] implementation.
111        impl PartialEq for $ty {
112            fn eq(&self, other: &Self) -> bool {
113                self.cmp(other).is_eq()
114            }
115        }
116
117        /// Equality comparison
118        ///
119        #[doc = concat!(
120                    "In order to be able to use [`", stringify!($ty), "`] as a mapping key, ",
121                    "floating values use [`", stringify!($float), "::total_cmp`] for a total ",
122                    "order comparison.",
123                )]
124        ///
125        /// See the [`Ord`] implementation.
126        impl Eq for $ty {}
127
128        impl Hash for $ty {
129            fn hash<H: Hasher>(&self, state: &mut H) {
130                self.0.to_bits().hash(state);
131            }
132        }
133
134        /// Partial ordering comparison
135        ///
136        #[doc = concat!(
137                    "In order to be able to use [`", stringify!($ty), "`] as a mapping key, ",
138                    "floating values use [`", stringify!($float), "::total_cmp`] for a total ",
139                    "order comparison.",
140                )]
141        ///
142        /// See the [`Ord`] implementation.
143        impl PartialOrd for $ty {
144            fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
145                Some(self.cmp(other))
146            }
147        }
148
149        /// Ordering comparison
150        ///
151        #[doc = concat!(
152                    "In order to be able to use [`", stringify!($ty), "`] as a mapping key, ",
153                    "floating values use [`", stringify!($float), "::total_cmp`] for a total ",
154                    "order comparison.",
155                )]
156        ///
157        /// ```
158        #[doc = concat!("use ron::value::", stringify!($ty), ";")]
159        #[doc = concat!(
160                    "assert!(", stringify!($ty), "::new(", stringify!($float), "::NAN) > ",
161                    stringify!($ty), "::new(", stringify!($float), "::INFINITY));",
162                )]
163        #[doc = concat!(
164                    "assert!(", stringify!($ty), "::new(-", stringify!($float), "::NAN) < ",
165                    stringify!($ty), "::new(", stringify!($float), "::NEG_INFINITY));",
166                )]
167        #[doc = concat!(
168                    "assert!(", stringify!($ty), "::new(", stringify!($float), "::NAN) == ",
169                    stringify!($ty), "::new(", stringify!($float), "::NAN));",
170                )]
171        /// ```
172        impl Ord for $ty {
173            fn cmp(&self, other: &Self) -> Ordering {
174                self.0.total_cmp(&other.0)
175            }
176        }
177    };
178}
179
180float_ty! { F32(f32) }
181float_ty! { F64(f64) }
182
183impl Number {
184    /// Construct a new number.
185    pub fn new(v: impl Into<Number>) -> Self {
186        v.into()
187    }
188
189    /// Returns the [`f64`] representation of the [`Number`] regardless of
190    /// whether the number is stored as a float or integer.
191    ///
192    /// # Example
193    ///
194    /// ```
195    /// # use ron::value::Number;
196    /// let i = Number::new(5);
197    /// let f = Number::new(2.0);
198    /// assert_eq!(i.into_f64(), 5.0);
199    /// assert_eq!(f.into_f64(), 2.0);
200    /// ```
201    #[must_use]
202    pub fn into_f64(self) -> f64 {
203        #[allow(clippy::cast_precision_loss)]
204        match self {
205            Number::I8(v) => f64::from(v),
206            Number::I16(v) => f64::from(v),
207            Number::I32(v) => f64::from(v),
208            Number::I64(v) => v as f64,
209            #[cfg(feature = "integer128")]
210            Number::I128(v) => v as f64,
211            Number::U8(v) => f64::from(v),
212            Number::U16(v) => f64::from(v),
213            Number::U32(v) => f64::from(v),
214            Number::U64(v) => v as f64,
215            #[cfg(feature = "integer128")]
216            Number::U128(v) => v as f64,
217            Number::F32(v) => f64::from(v.get()),
218            Number::F64(v) => v.get(),
219        }
220    }
221}
222
223macro_rules! number_from_impl {
224    (Number::$variant:ident($wrap:ident($ty:ty))) => {
225        impl From<$ty> for Number {
226            fn from(v: $ty) -> Number {
227                Number::$variant($wrap(v))
228            }
229        }
230    };
231    (Number::$variant:ident($ty:ty)) => {
232        impl From<$ty> for Number {
233            fn from(v: $ty) -> Number {
234                Number::$variant(v)
235            }
236        }
237    };
238}
239
240number_from_impl! { Number::I8(i8) }
241number_from_impl! { Number::I16(i16) }
242number_from_impl! { Number::I32(i32) }
243number_from_impl! { Number::I64(i64) }
244#[cfg(feature = "integer128")]
245number_from_impl! { Number::I128(i128) }
246number_from_impl! { Number::U8(u8) }
247number_from_impl! { Number::U16(u16) }
248number_from_impl! { Number::U32(u32) }
249number_from_impl! { Number::U64(u64) }
250#[cfg(feature = "integer128")]
251number_from_impl! { Number::U128(u128) }
252number_from_impl! { Number::F32(F32(f32)) }
253number_from_impl! { Number::F64(F64(f64)) }
254
255#[cfg(test)]
256mod tests {
257    use std::collections::hash_map::DefaultHasher;
258    use std::hash::{Hash, Hasher};
259
260    use super::*;
261
262    fn hash<T: Hash>(v: &T) -> u64 {
263        let mut state = DefaultHasher::new();
264        v.hash(&mut state);
265        state.finish()
266    }
267
268    #[test]
269    fn test_nan() {
270        assert_eq!(F32(f32::NAN), F32(f32::NAN));
271        assert_eq!(F32(-f32::NAN), F32(-f32::NAN));
272        assert_ne!(F32(f32::NAN), F32(-f32::NAN));
273
274        assert_eq!(hash(&F32(f32::NAN)), hash(&F32(f32::NAN)));
275        assert_eq!(hash(&F32(-f32::NAN)), hash(&F32(-f32::NAN)));
276        assert_ne!(hash(&F32(f32::NAN)), hash(&F32(-f32::NAN)));
277    }
278
279    #[test]
280    fn test_partial_ord() {
281        assert!(F32(f32::NAN) > F32(f32::INFINITY));
282        assert!(F32(-f32::NAN) < F32(f32::NEG_INFINITY));
283        assert!(F32(f32::NAN) == F32(f32::NAN));
284    }
285}