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}