Skip to main content

serde_yaml/
number.rs

1use std::{
2    cmp::Ordering,
3    fmt::{self, Display},
4    hash::{Hash, Hasher},
5    str::FromStr,
6};
7
8use serde::{
9    Deserialize, Deserializer, Serialize, Serializer,
10    de::{Unexpected, Visitor},
11    forward_to_deserialize_any,
12};
13
14use crate::{
15    de,
16    error::{self, Error, ErrorImpl},
17};
18
19/// Represents a YAML number, whether integer or floating point.
20#[derive(Clone, PartialEq, PartialOrd)]
21pub struct Number {
22    n: N,
23}
24
25// "N" is a prefix of "NegInt"... this is a false positive.
26// https://github.com/Manishearth/rust-clippy/issues/1241
27#[allow(clippy::enum_variant_names)]
28#[derive(Copy, Clone)]
29enum N {
30    PosInt(u64),
31    /// Always less than zero.
32    NegInt(i64),
33    /// May be infinite or NaN.
34    Float(f64),
35}
36
37impl Number {
38    /// Returns true if the `Number` is an integer between `i64::MIN` and
39    /// `i64::MAX`.
40    ///
41    /// For any Number on which `is_i64` returns true, `as_i64` is guaranteed to
42    /// return the integer value.
43    ///
44    /// ```
45    /// # fn main() -> serde_yaml::Result<()> {
46    /// let big = i64::MAX as u64 + 10;
47    /// let v: serde_yaml::Value = serde_yaml::from_str(r#"
48    /// a: 64
49    /// b: 9223372036854775817
50    /// c: 256.0
51    /// "#)?;
52    ///
53    /// assert!(v["a"].is_i64());
54    ///
55    /// // Greater than i64::MAX.
56    /// assert!(!v["b"].is_i64());
57    ///
58    /// // Numbers with a decimal point are not considered integers.
59    /// assert!(!v["c"].is_i64());
60    /// # Ok(())
61    /// # }
62    /// ```
63    #[inline]
64    #[allow(clippy::cast_sign_loss)]
65    pub fn is_i64(&self) -> bool {
66        match self.n {
67            N::PosInt(v) => v <= i64::max_value() as u64,
68            N::NegInt(_) => true,
69            N::Float(_) => false,
70        }
71    }
72
73    /// Returns true if the `Number` is an integer between zero and `u64::MAX`.
74    ///
75    /// For any Number on which `is_u64` returns true, `as_u64` is guaranteed to
76    /// return the integer value.
77    ///
78    /// ```
79    /// # fn main() -> serde_yaml::Result<()> {
80    /// let v: serde_yaml::Value = serde_yaml::from_str(r#"
81    /// a: 64
82    /// b: -64
83    /// c: 256.0
84    /// "#)?;
85    ///
86    /// assert!(v["a"].is_u64());
87    ///
88    /// // Negative integer.
89    /// assert!(!v["b"].is_u64());
90    ///
91    /// // Numbers with a decimal point are not considered integers.
92    /// assert!(!v["c"].is_u64());
93    /// # Ok(())
94    /// # }
95    /// ```
96    #[inline]
97    pub fn is_u64(&self) -> bool {
98        match self.n {
99            N::PosInt(_) => true,
100            N::NegInt(_) | N::Float(_) => false,
101        }
102    }
103
104    /// Returns true if the `Number` can be represented by f64.
105    ///
106    /// For any Number on which `is_f64` returns true, `as_f64` is guaranteed to
107    /// return the floating point value.
108    ///
109    /// Currently this function returns true if and only if both `is_i64` and
110    /// `is_u64` return false but this is not a guarantee in the future.
111    ///
112    /// ```
113    /// # fn main() -> serde_yaml::Result<()> {
114    /// let v: serde_yaml::Value = serde_yaml::from_str(r#"
115    /// a: 256.0
116    /// b: 64
117    /// c: -64
118    /// "#)?;
119    ///
120    /// assert!(v["a"].is_f64());
121    ///
122    /// // Integers.
123    /// assert!(!v["b"].is_f64());
124    /// assert!(!v["c"].is_f64());
125    /// # Ok(())
126    /// # }
127    /// ```
128    #[inline]
129    pub fn is_f64(&self) -> bool {
130        match self.n {
131            N::Float(_) => true,
132            N::PosInt(_) | N::NegInt(_) => false,
133        }
134    }
135
136    /// If the `Number` is an integer, represent it as i64 if possible. Returns
137    /// None otherwise.
138    ///
139    /// ```
140    /// # fn main() -> serde_yaml::Result<()> {
141    /// let big = i64::MAX as u64 + 10;
142    /// let v: serde_yaml::Value = serde_yaml::from_str(r#"
143    /// a: 64
144    /// b: 9223372036854775817
145    /// c: 256.0
146    /// "#)?;
147    ///
148    /// assert_eq!(v["a"].as_i64(), Some(64));
149    /// assert_eq!(v["b"].as_i64(), None);
150    /// assert_eq!(v["c"].as_i64(), None);
151    /// # Ok(())
152    /// # }
153    /// ```
154    #[inline]
155    pub fn as_i64(&self) -> Option<i64> {
156        match self.n {
157            N::PosInt(n) => {
158                if n <= i64::max_value() as u64 {
159                    Some(n as i64)
160                } else {
161                    None
162                }
163            }
164            N::NegInt(n) => Some(n),
165            N::Float(_) => None,
166        }
167    }
168
169    /// If the `Number` is an integer, represent it as u64 if possible. Returns
170    /// None otherwise.
171    ///
172    /// ```
173    /// # fn main() -> serde_yaml::Result<()> {
174    /// let v: serde_yaml::Value = serde_yaml::from_str(r#"
175    /// a: 64
176    /// b: -64
177    /// c: 256.0
178    /// "#)?;
179    ///
180    /// assert_eq!(v["a"].as_u64(), Some(64));
181    /// assert_eq!(v["b"].as_u64(), None);
182    /// assert_eq!(v["c"].as_u64(), None);
183    /// # Ok(())
184    /// # }
185    /// ```
186    #[inline]
187    pub fn as_u64(&self) -> Option<u64> {
188        match self.n {
189            N::PosInt(n) => Some(n),
190            N::NegInt(_) | N::Float(_) => None,
191        }
192    }
193
194    /// Represents the number as f64 if possible. Returns None otherwise.
195    ///
196    /// ```
197    /// # fn main() -> serde_yaml::Result<()> {
198    /// let v: serde_yaml::Value = serde_yaml::from_str(r#"
199    /// a: 256.0
200    /// b: 64
201    /// c: -64
202    /// "#)?;
203    ///
204    /// assert_eq!(v["a"].as_f64(), Some(256.0));
205    /// assert_eq!(v["b"].as_f64(), Some(64.0));
206    /// assert_eq!(v["c"].as_f64(), Some(-64.0));
207    /// # Ok(())
208    /// # }
209    /// ```
210    ///
211    /// ```
212    /// # fn main() -> serde_yaml::Result<()> {
213    /// let v: serde_yaml::Value = serde_yaml::from_str(".inf")?;
214    /// assert_eq!(v.as_f64(), Some(f64::INFINITY));
215    ///
216    /// let v: serde_yaml::Value = serde_yaml::from_str("-.inf")?;
217    /// assert_eq!(v.as_f64(), Some(f64::NEG_INFINITY));
218    ///
219    /// let v: serde_yaml::Value = serde_yaml::from_str(".nan")?;
220    /// assert!(v.as_f64().unwrap().is_nan());
221    /// # Ok(())
222    /// # }
223    /// ```
224    #[inline]
225    pub fn as_f64(&self) -> Option<f64> {
226        match self.n {
227            N::PosInt(n) => Some(n as f64),
228            N::NegInt(n) => Some(n as f64),
229            N::Float(n) => Some(n),
230        }
231    }
232
233    /// Returns true if this value is NaN and false otherwise.
234    ///
235    /// ```
236    /// # use serde_yaml::Number;
237    /// #
238    /// assert!(!Number::from(256.0).is_nan());
239    ///
240    /// assert!(Number::from(f64::NAN).is_nan());
241    ///
242    /// assert!(!Number::from(f64::INFINITY).is_nan());
243    ///
244    /// assert!(!Number::from(f64::NEG_INFINITY).is_nan());
245    ///
246    /// assert!(!Number::from(1).is_nan());
247    /// ```
248    #[inline]
249    pub fn is_nan(&self) -> bool {
250        match self.n {
251            N::PosInt(_) | N::NegInt(_) => false,
252            N::Float(f) => f.is_nan(),
253        }
254    }
255
256    /// Returns true if this value is positive infinity or negative infinity and
257    /// false otherwise.
258    ///
259    /// ```
260    /// # use serde_yaml::Number;
261    /// #
262    /// assert!(!Number::from(256.0).is_infinite());
263    ///
264    /// assert!(!Number::from(f64::NAN).is_infinite());
265    ///
266    /// assert!(Number::from(f64::INFINITY).is_infinite());
267    ///
268    /// assert!(Number::from(f64::NEG_INFINITY).is_infinite());
269    ///
270    /// assert!(!Number::from(1).is_infinite());
271    /// ```
272    #[inline]
273    pub fn is_infinite(&self) -> bool {
274        match self.n {
275            N::PosInt(_) | N::NegInt(_) => false,
276            N::Float(f) => f.is_infinite(),
277        }
278    }
279
280    /// Returns true if this number is neither infinite nor NaN.
281    ///
282    /// ```
283    /// # use serde_yaml::Number;
284    /// #
285    /// assert!(Number::from(256.0).is_finite());
286    ///
287    /// assert!(!Number::from(f64::NAN).is_finite());
288    ///
289    /// assert!(!Number::from(f64::INFINITY).is_finite());
290    ///
291    /// assert!(!Number::from(f64::NEG_INFINITY).is_finite());
292    ///
293    /// assert!(Number::from(1).is_finite());
294    /// ```
295    #[inline]
296    pub fn is_finite(&self) -> bool {
297        match self.n {
298            N::PosInt(_) | N::NegInt(_) => true,
299            N::Float(f) => f.is_finite(),
300        }
301    }
302}
303
304impl Display for Number {
305    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
306        match self.n {
307            N::PosInt(i) => {
308                let mut buf = core::fmt::NumBuffer::new();
309                formatter.write_str(i.format_into(&mut buf))
310            }
311            N::NegInt(i) => {
312                let mut buf = core::fmt::NumBuffer::new();
313                formatter.write_str(i.format_into(&mut buf))
314            }
315            N::Float(f) if f.is_nan() => formatter.write_str(".nan"),
316            N::Float(f) if f.is_infinite() => {
317                if f.is_sign_negative() {
318                    formatter.write_str("-.inf")
319                } else {
320                    formatter.write_str(".inf")
321                }
322            }
323            N::Float(f) => formatter.write_str(ryu::Buffer::new().format_finite(f)),
324        }
325    }
326}
327
328impl FromStr for Number {
329    type Err = Error;
330
331    fn from_str(repr: &str) -> Result<Self, Self::Err> {
332        if let Ok(result) = de::visit_int(NumberVisitor, repr) {
333            return result;
334        }
335        if !de::digits_but_not_number(repr) {
336            if let Some(float) = de::parse_f64(repr) {
337                return Ok(float.into());
338            }
339        }
340        Err(error::new(ErrorImpl::FailedToParseNumber))
341    }
342}
343
344impl PartialEq for N {
345    fn eq(&self, other: &N) -> bool {
346        match (*self, *other) {
347            (N::PosInt(a), N::PosInt(b)) => a == b,
348            (N::NegInt(a), N::NegInt(b)) => a == b,
349            (N::Float(a), N::Float(b)) => {
350                if a.is_nan() && b.is_nan() {
351                    // YAML only has one NaN;
352                    // the bit representation isn't preserved
353                    true
354                } else {
355                    a == b
356                }
357            }
358            _ => false,
359        }
360    }
361}
362
363impl PartialOrd for N {
364    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
365        match (*self, *other) {
366            (N::Float(a), N::Float(b)) => {
367                if a.is_nan() && b.is_nan() {
368                    // YAML only has one NaN
369                    Some(Ordering::Equal)
370                } else {
371                    a.partial_cmp(&b)
372                }
373            }
374            _ => Some(self.total_cmp(other)),
375        }
376    }
377}
378
379impl N {
380    fn total_cmp(&self, other: &Self) -> Ordering {
381        match (*self, *other) {
382            (N::PosInt(a), N::PosInt(b)) => a.cmp(&b),
383            (N::NegInt(a), N::NegInt(b)) => a.cmp(&b),
384            // negint is always less than zero
385            (N::NegInt(_), N::PosInt(_)) => Ordering::Less,
386            (N::PosInt(_), N::NegInt(_)) => Ordering::Greater,
387            (N::Float(a), N::Float(b)) => a.partial_cmp(&b).unwrap_or_else(|| {
388                // arbitrarily sort the NaN last
389                if !a.is_nan() {
390                    Ordering::Less
391                } else if !b.is_nan() {
392                    Ordering::Greater
393                } else {
394                    Ordering::Equal
395                }
396            }),
397            // arbitrarily sort integers below floats
398            // FIXME: maybe something more sensible?
399            (_, N::Float(_)) => Ordering::Less,
400            (N::Float(_), _) => Ordering::Greater,
401        }
402    }
403}
404
405impl Number {
406    pub(crate) fn total_cmp(&self, other: &Self) -> Ordering {
407        self.n.total_cmp(&other.n)
408    }
409}
410
411impl Serialize for Number {
412    #[inline]
413    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
414    where
415        S: Serializer,
416    {
417        match self.n {
418            N::PosInt(i) => serializer.serialize_u64(i),
419            N::NegInt(i) => serializer.serialize_i64(i),
420            N::Float(f) => serializer.serialize_f64(f),
421        }
422    }
423}
424
425struct NumberVisitor;
426
427impl<'de> Visitor<'de> for NumberVisitor {
428    type Value = Number;
429
430    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
431        formatter.write_str("a number")
432    }
433
434    #[inline]
435    fn visit_i64<E>(self, value: i64) -> Result<Number, E> {
436        Ok(value.into())
437    }
438
439    #[inline]
440    fn visit_u64<E>(self, value: u64) -> Result<Number, E> {
441        Ok(value.into())
442    }
443
444    #[inline]
445    fn visit_f64<E>(self, value: f64) -> Result<Number, E> {
446        Ok(value.into())
447    }
448}
449
450impl<'de> Deserialize<'de> for Number {
451    #[inline]
452    fn deserialize<D>(deserializer: D) -> Result<Number, D::Error>
453    where
454        D: Deserializer<'de>,
455    {
456        deserializer.deserialize_any(NumberVisitor)
457    }
458}
459
460impl<'de> Deserializer<'de> for Number {
461    type Error = Error;
462
463    #[inline]
464    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
465    where
466        V: Visitor<'de>,
467    {
468        match self.n {
469            N::PosInt(i) => visitor.visit_u64(i),
470            N::NegInt(i) => visitor.visit_i64(i),
471            N::Float(f) => visitor.visit_f64(f),
472        }
473    }
474
475    forward_to_deserialize_any! {
476        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
477        bytes byte_buf option unit unit_struct newtype_struct seq tuple
478        tuple_struct map struct enum identifier ignored_any
479    }
480}
481
482impl<'de, 'a> Deserializer<'de> for &'a Number {
483    type Error = Error;
484
485    #[inline]
486    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
487    where
488        V: Visitor<'de>,
489    {
490        match self.n {
491            N::PosInt(i) => visitor.visit_u64(i),
492            N::NegInt(i) => visitor.visit_i64(i),
493            N::Float(f) => visitor.visit_f64(f),
494        }
495    }
496
497    forward_to_deserialize_any! {
498        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
499        bytes byte_buf option unit unit_struct newtype_struct seq tuple
500        tuple_struct map struct enum identifier ignored_any
501    }
502}
503
504macro_rules! from_signed {
505    ($($signed_ty:ident)*) => {
506        $(
507            impl From<$signed_ty> for Number {
508                #[inline]
509                #[allow(clippy::cast_sign_loss)]
510                fn from(i: $signed_ty) -> Self {
511                    if i < 0 {
512                        Number { n: N::NegInt(i as i64) }
513                    } else {
514                        Number { n: N::PosInt(i as u64) }
515                    }
516                }
517            }
518        )*
519    };
520}
521
522macro_rules! from_unsigned {
523    ($($unsigned_ty:ident)*) => {
524        $(
525            impl From<$unsigned_ty> for Number {
526                #[inline]
527                fn from(u: $unsigned_ty) -> Self {
528                    Number { n: N::PosInt(u as u64) }
529                }
530            }
531        )*
532    };
533}
534
535from_signed!(i8 i16 i32 i64 isize);
536from_unsigned!(u8 u16 u32 u64 usize);
537
538impl From<f32> for Number {
539    fn from(f: f32) -> Self {
540        Number::from(f as f64)
541    }
542}
543
544impl From<f64> for Number {
545    fn from(mut f: f64) -> Self {
546        if f.is_nan() {
547            // Destroy NaN sign, signaling, and payload. YAML only has one NaN.
548            f = f64::NAN.copysign(1.0);
549        }
550        Number {
551            n: N::Float(f),
552        }
553    }
554}
555
556// This is fine, because we don't _really_ implement hash for floats
557// all other hash functions should work as expected
558#[allow(clippy::derived_hash_with_manual_eq)]
559impl Hash for Number {
560    fn hash<H: Hasher>(&self, state: &mut H) {
561        match self.n {
562            N::Float(_) => {
563                // you should feel bad for using f64 as a map key
564                3.hash(state);
565            }
566            N::PosInt(u) => u.hash(state),
567            N::NegInt(i) => i.hash(state),
568        }
569    }
570}
571
572pub(crate) fn unexpected(number: &Number) -> Unexpected<'_> {
573    match number.n {
574        N::PosInt(u) => Unexpected::Unsigned(u),
575        N::NegInt(i) => Unexpected::Signed(i),
576        N::Float(f) => Unexpected::Float(f),
577    }
578}