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) => formatter.write_str(itoa::Buffer::new().format(i)),
308            N::NegInt(i) => formatter.write_str(itoa::Buffer::new().format(i)),
309            N::Float(f) if f.is_nan() => formatter.write_str(".nan"),
310            N::Float(f) if f.is_infinite() => {
311                if f.is_sign_negative() {
312                    formatter.write_str("-.inf")
313                } else {
314                    formatter.write_str(".inf")
315                }
316            }
317            N::Float(f) => formatter.write_str(ryu::Buffer::new().format_finite(f)),
318        }
319    }
320}
321
322impl FromStr for Number {
323    type Err = Error;
324
325    fn from_str(repr: &str) -> Result<Self, Self::Err> {
326        if let Ok(result) = de::visit_int(NumberVisitor, repr) {
327            return result;
328        }
329        if !de::digits_but_not_number(repr) {
330            if let Some(float) = de::parse_f64(repr) {
331                return Ok(float.into());
332            }
333        }
334        Err(error::new(ErrorImpl::FailedToParseNumber))
335    }
336}
337
338impl PartialEq for N {
339    fn eq(&self, other: &N) -> bool {
340        match (*self, *other) {
341            (N::PosInt(a), N::PosInt(b)) => a == b,
342            (N::NegInt(a), N::NegInt(b)) => a == b,
343            (N::Float(a), N::Float(b)) => {
344                if a.is_nan() && b.is_nan() {
345                    // YAML only has one NaN;
346                    // the bit representation isn't preserved
347                    true
348                } else {
349                    a == b
350                }
351            }
352            _ => false,
353        }
354    }
355}
356
357impl PartialOrd for N {
358    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
359        match (*self, *other) {
360            (N::Float(a), N::Float(b)) => {
361                if a.is_nan() && b.is_nan() {
362                    // YAML only has one NaN
363                    Some(Ordering::Equal)
364                } else {
365                    a.partial_cmp(&b)
366                }
367            }
368            _ => Some(self.total_cmp(other)),
369        }
370    }
371}
372
373impl N {
374    fn total_cmp(&self, other: &Self) -> Ordering {
375        match (*self, *other) {
376            (N::PosInt(a), N::PosInt(b)) => a.cmp(&b),
377            (N::NegInt(a), N::NegInt(b)) => a.cmp(&b),
378            // negint is always less than zero
379            (N::NegInt(_), N::PosInt(_)) => Ordering::Less,
380            (N::PosInt(_), N::NegInt(_)) => Ordering::Greater,
381            (N::Float(a), N::Float(b)) => a.partial_cmp(&b).unwrap_or_else(|| {
382                // arbitrarily sort the NaN last
383                if !a.is_nan() {
384                    Ordering::Less
385                } else if !b.is_nan() {
386                    Ordering::Greater
387                } else {
388                    Ordering::Equal
389                }
390            }),
391            // arbitrarily sort integers below floats
392            // FIXME: maybe something more sensible?
393            (_, N::Float(_)) => Ordering::Less,
394            (N::Float(_), _) => Ordering::Greater,
395        }
396    }
397}
398
399impl Number {
400    pub(crate) fn total_cmp(&self, other: &Self) -> Ordering {
401        self.n.total_cmp(&other.n)
402    }
403}
404
405impl Serialize for Number {
406    #[inline]
407    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
408    where
409        S: Serializer,
410    {
411        match self.n {
412            N::PosInt(i) => serializer.serialize_u64(i),
413            N::NegInt(i) => serializer.serialize_i64(i),
414            N::Float(f) => serializer.serialize_f64(f),
415        }
416    }
417}
418
419struct NumberVisitor;
420
421impl<'de> Visitor<'de> for NumberVisitor {
422    type Value = Number;
423
424    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
425        formatter.write_str("a number")
426    }
427
428    #[inline]
429    fn visit_i64<E>(self, value: i64) -> Result<Number, E> {
430        Ok(value.into())
431    }
432
433    #[inline]
434    fn visit_u64<E>(self, value: u64) -> Result<Number, E> {
435        Ok(value.into())
436    }
437
438    #[inline]
439    fn visit_f64<E>(self, value: f64) -> Result<Number, E> {
440        Ok(value.into())
441    }
442}
443
444impl<'de> Deserialize<'de> for Number {
445    #[inline]
446    fn deserialize<D>(deserializer: D) -> Result<Number, D::Error>
447    where
448        D: Deserializer<'de>,
449    {
450        deserializer.deserialize_any(NumberVisitor)
451    }
452}
453
454impl<'de> Deserializer<'de> for Number {
455    type Error = Error;
456
457    #[inline]
458    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
459    where
460        V: Visitor<'de>,
461    {
462        match self.n {
463            N::PosInt(i) => visitor.visit_u64(i),
464            N::NegInt(i) => visitor.visit_i64(i),
465            N::Float(f) => visitor.visit_f64(f),
466        }
467    }
468
469    forward_to_deserialize_any! {
470        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
471        bytes byte_buf option unit unit_struct newtype_struct seq tuple
472        tuple_struct map struct enum identifier ignored_any
473    }
474}
475
476impl<'de, 'a> Deserializer<'de> for &'a Number {
477    type Error = Error;
478
479    #[inline]
480    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
481    where
482        V: Visitor<'de>,
483    {
484        match self.n {
485            N::PosInt(i) => visitor.visit_u64(i),
486            N::NegInt(i) => visitor.visit_i64(i),
487            N::Float(f) => visitor.visit_f64(f),
488        }
489    }
490
491    forward_to_deserialize_any! {
492        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
493        bytes byte_buf option unit unit_struct newtype_struct seq tuple
494        tuple_struct map struct enum identifier ignored_any
495    }
496}
497
498macro_rules! from_signed {
499    ($($signed_ty:ident)*) => {
500        $(
501            impl From<$signed_ty> for Number {
502                #[inline]
503                #[allow(clippy::cast_sign_loss)]
504                fn from(i: $signed_ty) -> Self {
505                    if i < 0 {
506                        Number { n: N::NegInt(i as i64) }
507                    } else {
508                        Number { n: N::PosInt(i as u64) }
509                    }
510                }
511            }
512        )*
513    };
514}
515
516macro_rules! from_unsigned {
517    ($($unsigned_ty:ident)*) => {
518        $(
519            impl From<$unsigned_ty> for Number {
520                #[inline]
521                fn from(u: $unsigned_ty) -> Self {
522                    Number { n: N::PosInt(u as u64) }
523                }
524            }
525        )*
526    };
527}
528
529from_signed!(i8 i16 i32 i64 isize);
530from_unsigned!(u8 u16 u32 u64 usize);
531
532impl From<f32> for Number {
533    fn from(f: f32) -> Self {
534        Number::from(f as f64)
535    }
536}
537
538impl From<f64> for Number {
539    fn from(mut f: f64) -> Self {
540        if f.is_nan() {
541            // Destroy NaN sign, signaling, and payload. YAML only has one NaN.
542            f = f64::NAN.copysign(1.0);
543        }
544        Number {
545            n: N::Float(f),
546        }
547    }
548}
549
550// This is fine, because we don't _really_ implement hash for floats
551// all other hash functions should work as expected
552#[allow(clippy::derived_hash_with_manual_eq)]
553impl Hash for Number {
554    fn hash<H: Hasher>(&self, state: &mut H) {
555        match self.n {
556            N::Float(_) => {
557                // you should feel bad for using f64 as a map key
558                3.hash(state);
559            }
560            N::PosInt(u) => u.hash(state),
561            N::NegInt(i) => i.hash(state),
562        }
563    }
564}
565
566pub(crate) fn unexpected(number: &Number) -> Unexpected {
567    match number.n {
568        N::PosInt(u) => Unexpected::Unsigned(u),
569        N::NegInt(i) => Unexpected::Signed(i),
570        N::Float(f) => Unexpected::Float(f),
571    }
572}