Skip to main content

yaml_rt_serde/value/
number.rs

1use std::cmp::Ordering;
2use std::fmt;
3use std::hash::{Hash, Hasher};
4use std::str::FromStr;
5
6use serde::de::{self, Visitor};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9use crate::{Error, Result};
10
11/// A YAML integer or floating-point number.
12///
13/// Unlike `yaml_serde::Number`, this type retains the full Serde `i128` and
14/// `u128` ranges.
15#[derive(Clone, Copy)]
16pub struct Number {
17    repr: Repr,
18}
19
20#[derive(Clone, Copy)]
21enum Repr {
22    Signed(i128),
23    Unsigned(u128),
24    Float(f64),
25}
26
27impl Number {
28    pub(crate) const fn signed(value: i128) -> Self {
29        Self {
30            repr: Repr::Signed(value),
31        }
32    }
33
34    pub(crate) const fn unsigned(value: u128) -> Self {
35        Self {
36            repr: Repr::Unsigned(value),
37        }
38    }
39
40    pub(crate) const fn float(value: f64) -> Self {
41        Self {
42            repr: Repr::Float(value),
43        }
44    }
45
46    /// Returns true when this is an integer representable as `i64`.
47    #[must_use]
48    pub fn is_i64(&self) -> bool {
49        self.as_i64().is_some()
50    }
51
52    /// Returns true when this is an integer representable as `u64`.
53    #[must_use]
54    pub fn is_u64(&self) -> bool {
55        self.as_u64().is_some()
56    }
57
58    /// Returns true when this was represented as a floating-point number.
59    #[must_use]
60    pub const fn is_f64(&self) -> bool {
61        matches!(self.repr, Repr::Float(_))
62    }
63
64    /// Returns true when this is an integer representable as `i128`.
65    #[must_use]
66    pub fn is_i128(&self) -> bool {
67        self.as_i128().is_some()
68    }
69
70    /// Returns true when this is an integer representable as `u128`.
71    #[must_use]
72    pub fn is_u128(&self) -> bool {
73        self.as_u128().is_some()
74    }
75
76    /// Returns the integer as `i64` when it fits.
77    #[must_use]
78    pub fn as_i64(&self) -> Option<i64> {
79        self.as_i128().and_then(|value| i64::try_from(value).ok())
80    }
81
82    /// Returns the integer as `u64` when it fits.
83    #[must_use]
84    pub fn as_u64(&self) -> Option<u64> {
85        self.as_u128().and_then(|value| u64::try_from(value).ok())
86    }
87
88    /// Returns the integer as `i128` when it fits.
89    #[must_use]
90    pub fn as_i128(&self) -> Option<i128> {
91        match self.repr {
92            Repr::Signed(value) => Some(value),
93            Repr::Unsigned(value) => i128::try_from(value).ok(),
94            Repr::Float(_) => None,
95        }
96    }
97
98    /// Returns the integer as `u128` when it is non-negative.
99    #[must_use]
100    pub fn as_u128(&self) -> Option<u128> {
101        match self.repr {
102            Repr::Signed(value) => u128::try_from(value).ok(),
103            Repr::Unsigned(value) => Some(value),
104            Repr::Float(_) => None,
105        }
106    }
107
108    /// Returns this number as `f64`.
109    #[must_use]
110    pub fn as_f64(&self) -> Option<f64> {
111        Some(match self.repr {
112            Repr::Signed(value) => value as f64,
113            Repr::Unsigned(value) => value as f64,
114            Repr::Float(value) => value,
115        })
116    }
117
118    /// Returns true when this is NaN.
119    #[must_use]
120    pub fn is_nan(&self) -> bool {
121        matches!(self.repr, Repr::Float(value) if value.is_nan())
122    }
123
124    /// Returns true when this is positive or negative infinity.
125    #[must_use]
126    pub fn is_infinite(&self) -> bool {
127        matches!(self.repr, Repr::Float(value) if value.is_infinite())
128    }
129
130    /// Returns true when this is not infinity or NaN.
131    #[must_use]
132    pub fn is_finite(&self) -> bool {
133        !matches!(self.repr, Repr::Float(value) if !value.is_finite())
134    }
135}
136
137impl PartialEq for Number {
138    fn eq(&self, other: &Self) -> bool {
139        match (self.repr, other.repr) {
140            (Repr::Signed(left), Repr::Signed(right)) => left == right,
141            (Repr::Unsigned(left), Repr::Unsigned(right)) => left == right,
142            (Repr::Signed(left), Repr::Unsigned(right))
143            | (Repr::Unsigned(right), Repr::Signed(left)) => {
144                u128::try_from(left).is_ok_and(|left| left == right)
145            }
146            (Repr::Float(left), Repr::Float(right)) => left == right,
147            _ => false,
148        }
149    }
150}
151
152impl PartialOrd for Number {
153    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
154        match (self.repr, other.repr) {
155            (Repr::Signed(left), Repr::Signed(right)) => left.partial_cmp(&right),
156            (Repr::Unsigned(left), Repr::Unsigned(right)) => left.partial_cmp(&right),
157            (Repr::Signed(left), Repr::Unsigned(right)) => {
158                if left < 0 {
159                    Some(Ordering::Less)
160                } else {
161                    (left as u128).partial_cmp(&right)
162                }
163            }
164            (Repr::Unsigned(left), Repr::Signed(right)) => {
165                if right < 0 {
166                    Some(Ordering::Greater)
167                } else {
168                    left.partial_cmp(&(right as u128))
169                }
170            }
171            (Repr::Float(left), Repr::Float(right)) => left.partial_cmp(&right),
172            (left, right) => repr_as_f64(left).partial_cmp(&repr_as_f64(right)),
173        }
174    }
175}
176
177fn repr_as_f64(value: Repr) -> f64 {
178    match value {
179        Repr::Signed(value) => value as f64,
180        Repr::Unsigned(value) => value as f64,
181        Repr::Float(value) => value,
182    }
183}
184
185impl Hash for Number {
186    fn hash<H: Hasher>(&self, state: &mut H) {
187        match self.repr {
188            Repr::Signed(value) if value >= 0 => {
189                0_u8.hash(state);
190                (value as u128).hash(state);
191            }
192            Repr::Signed(value) => {
193                1_u8.hash(state);
194                value.hash(state);
195            }
196            Repr::Unsigned(value) => {
197                0_u8.hash(state);
198                value.hash(state);
199            }
200            Repr::Float(value) => {
201                2_u8.hash(state);
202                let bits = if value == 0.0 { 0 } else { value.to_bits() };
203                bits.hash(state);
204            }
205        }
206    }
207}
208
209impl fmt::Display for Number {
210    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
211        match self.repr {
212            Repr::Signed(value) => value.fmt(formatter),
213            Repr::Unsigned(value) => value.fmt(formatter),
214            Repr::Float(value) if value.is_nan() => formatter.write_str(".nan"),
215            Repr::Float(value) if value == f64::INFINITY => formatter.write_str(".inf"),
216            Repr::Float(value) if value == f64::NEG_INFINITY => formatter.write_str("-.inf"),
217            Repr::Float(value) => {
218                let text = value.to_string();
219                formatter.write_str(&text)?;
220                if !text.contains(['.', 'e', 'E']) {
221                    formatter.write_str(".0")?;
222                }
223                Ok(())
224            }
225        }
226    }
227}
228
229impl fmt::Debug for Number {
230    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
231        fmt::Display::fmt(self, formatter)
232    }
233}
234
235impl FromStr for Number {
236    type Err = Error;
237
238    fn from_str(value: &str) -> Result<Self> {
239        match crate::from_str::<super::Value>(value)? {
240            super::Value::Number(number) => Ok(number),
241            _ => Err(Error::message("expected a YAML number")),
242        }
243    }
244}
245
246macro_rules! from_signed {
247    ($($ty:ty),+ $(,)?) => {
248        $(
249            impl From<$ty> for Number {
250                fn from(value: $ty) -> Self {
251                    Self::signed(value as i128)
252                }
253            }
254        )+
255    };
256}
257
258macro_rules! from_unsigned {
259    ($($ty:ty),+ $(,)?) => {
260        $(
261            impl From<$ty> for Number {
262                fn from(value: $ty) -> Self {
263                    Self::unsigned(value as u128)
264                }
265            }
266        )+
267    };
268}
269
270from_signed!(i8, i16, i32, i64, i128, isize);
271from_unsigned!(u8, u16, u32, u64, u128, usize);
272
273impl From<f32> for Number {
274    fn from(value: f32) -> Self {
275        Self::float(value.into())
276    }
277}
278
279impl From<f64> for Number {
280    fn from(value: f64) -> Self {
281        Self::float(value)
282    }
283}
284
285impl Serialize for Number {
286    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
287    where
288        S: Serializer,
289    {
290        match self.repr {
291            Repr::Signed(value) => serializer.serialize_i128(value),
292            Repr::Unsigned(value) => serializer.serialize_u128(value),
293            Repr::Float(value) => serializer.serialize_f64(value),
294        }
295    }
296}
297
298impl<'de> Deserialize<'de> for Number {
299    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
300    where
301        D: Deserializer<'de>,
302    {
303        struct NumberVisitor;
304
305        impl<'de> Visitor<'de> for NumberVisitor {
306            type Value = Number;
307
308            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
309                formatter.write_str("a YAML number")
310            }
311
312            fn visit_i64<E>(self, value: i64) -> std::result::Result<Number, E> {
313                Ok(Number::from(value))
314            }
315
316            fn visit_i128<E>(self, value: i128) -> std::result::Result<Number, E> {
317                Ok(Number::from(value))
318            }
319
320            fn visit_u64<E>(self, value: u64) -> std::result::Result<Number, E> {
321                Ok(Number::from(value))
322            }
323
324            fn visit_u128<E>(self, value: u128) -> std::result::Result<Number, E> {
325                Ok(Number::from(value))
326            }
327
328            fn visit_f64<E>(self, value: f64) -> std::result::Result<Number, E> {
329                Ok(Number::from(value))
330            }
331        }
332
333        deserializer.deserialize_any(NumberVisitor)
334    }
335}
336
337impl<'de> de::IntoDeserializer<'de, Error> for Number {
338    type Deserializer = super::Value;
339
340    fn into_deserializer(self) -> Self::Deserializer {
341        super::Value::Number(self)
342    }
343}