Repository: ron-rs/ron (rust)

Issue:
f64::INFINITY causes panic
# Problem summary

I need to be able to serialize and deserialize `f64::INFINITY` in my code.  I believe that this should be possible, but the following code causes a panic when run:

```rust
use ron::de;
use ron::ser;
use serde::*;
use std::cmp::{Eq, PartialEq};
use std::f64;

#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
pub struct Test1 {
    field: f64,
}

impl PartialEq for Test1 {
    fn eq(&self, other: &Test1) -> bool {
        self.field == other.field
    }
}

impl Eq for Test1 {}

fn main() {
    let original = Test1 {
        field: f64::INFINITY,
    };

    let pretty: ser::PrettyConfig = Default::default();
    let serialized = ser::to_string_pretty(&original, pretty.clone()).unwrap();
    let duplicate: Test1 = de::from_str(&serialized).unwrap();
    assert_eq!(original, duplicate);
}
```
There is no panic for finite `f64` values.

The panic:
```
$ cargo run
   Compiling serde_float_issue v0.1.0 (/home/cfkaran2/Desktop/bug_reports/serde_float_issue)
    Finished dev [unoptimized + debuginfo] target(s) in 0.49s
     Running `target/debug/serde_float_issue`
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Parser(ExpectedFloat, Position { col: 12, line: 2 })', src/libcore/result.rs:997:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
```

# Meta information:

```$ uname -a ; echo ; lsb_release --all ; echo ; rustc -Vv ; echo ; cargo -Vv
Linux EMANE 4.15.0-47-generic #50-Ubuntu SMP Wed Mar 13 10:44:52 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 18.04.2 LTS
Release:	18.04
Codename:	bionic

rustc 1.34.0 (91856ed52 2019-04-10)
binary: rustc
commit-hash: 91856ed52c58aa5ba66a015354d1cc69e9779bdf
commit-date: 2019-04-10
host: x86_64-unknown-linux-gnu
release: 1.34.0
LLVM version: 8.0

cargo 1.34.0 (6789d8a0a 2019-04-01)
release: 1.34.0
commit-hash: 6789d8a0a54a96d95365c4e1fb01d47a5eed9937
commit-date: 2019-04-01
```


Pull request description:
Implement inf, -inf and NaN handling
FIxes #159

Patch:
diff --git a/src/de/mod.rs b/src/de/mod.rs
index ae0e706c..cd4ad3cc 100644
--- a/src/de/mod.rs
+++ b/src/de/mod.rs
@@ -132,6 +132,12 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
             return visitor.visit_none();
         } else if self.bytes.consume("()") {
             return visitor.visit_unit();
+        } else if self.bytes.consume_ident("inf") {
+            return visitor.visit_f64(std::f64::INFINITY);
+        } else if self.bytes.consume_ident("-inf") {
+            return visitor.visit_f64(std::f64::NEG_INFINITY);
+        } else if self.bytes.consume_ident("NaN") {
+            return visitor.visit_f64(std::f64::NAN);
         }
 
         // `identifier` does not change state if it fails
diff --git a/src/de/value.rs b/src/de/value.rs
index 00b3596e..ddd50ca8 100644
--- a/src/de/value.rs
+++ b/src/de/value.rs
@@ -220,6 +220,18 @@ mod tests {
         );
     }
 
+    #[test]
+    fn test_floats() {
+        assert_eq!(
+            eval("(inf, -inf, NaN)"),
+            Value::Seq(vec![
+                Value::Number(Number::new(std::f64::INFINITY)),
+                Value::Number(Number::new(std::f64::NEG_INFINITY)),
+                Value::Number(Number::new(std::f64::NAN)),
+            ]),
+        );
+    }
+
     #[test]
     fn test_complex() {
         assert_eq!(
diff --git a/src/parse.rs b/src/parse.rs
index 9f76ed14..5593a872 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -364,6 +364,13 @@ impl<'a> Bytes<'a> {
     where
         T: FromStr,
     {
+        for literal in &["inf", "-inf", "NaN"] {
+            if self.consume_ident(literal) {
+                return FromStr::from_str(literal)
+                    .map_err(|_| unreachable!()); // must not fail
+            }
+        }
+
         let num_bytes = self.next_bytes_contained_in(FLOAT_CHARS);
 
         let s = unsafe { from_utf8_unchecked(&self.bytes[0..num_bytes]) };
diff --git a/src/value.rs b/src/value.rs
index cebac85b..3167a36c 100644
--- a/src/value.rs
+++ b/src/value.rs
@@ -16,17 +16,11 @@ use crate::de::{Error as RonError, Result};
 
 /// A wrapper for `f64` which guarantees that the inner value
 /// is finite and thus implements `Eq`, `Hash` and `Ord`.
-#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)]
+#[derive(Copy, Clone, Debug)]
 pub struct Number(f64);
 
 impl Number {
-    /// Panics if `v` is not a real number
-    /// (infinity, NaN, ..).
     pub fn new(v: f64) -> Self {
-        if !v.is_finite() {
-            panic!("Tried to create Number with a NaN / infinity");
-        }
-
         Number(v)
     }
 
@@ -36,6 +30,22 @@ impl Number {
     }
 }
 
+/// Partial equality comparison
+/// In order to be able to use `Number` as a mapping key, NaN floating values wrapped in `Number`
+/// are equals to each other. It is not the case for underlying `f64` values itself.
+impl PartialEq for Number {
+
+    fn eq(&self, other: &Self) -> bool {
+        if self.0.is_nan() && other.0.is_nan() {
+            return true;
+        }
+        return self.0 == other.0;
+    }
+}
+
+/// Equality comparison
+/// In order to be able to use `Number` as a mapping key, NaN floating values wrapped in `Number`
+/// are equals to each other. It is not the case for underlying `f64` values itself.
 impl Eq for Number {}
 
 impl Hash for Number {
@@ -44,6 +54,30 @@ impl Hash for Number {
     }
 }
 
+/// Partial ordering comparison
+/// In order to be able to use `Number` as a mapping key, NaN floating values wrapped in `Number`
+/// are equals to each other and are less then any other floating value.
+/// It is not the case for underlying `f64` values itself.
+/// ```
+/// use ron::value::Number;
+/// assert!(Number::new(std::f64::NAN) < Number::new(std::f64::NEG_INFINITY));
+/// assert_eq!(Number::new(std::f64::NAN), Number::new(std::f64::NAN));
+/// ```
+impl PartialOrd for Number {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        match (self.0.is_nan(), other.0.is_nan()) {
+            (true, true) => Some(Ordering::Equal),
+            (true, false) => Some(Ordering::Less),
+            (false, true) => Some(Ordering::Greater),
+            _ => self.0.partial_cmp(&other.0)
+        }
+    }
+}
+
+/// Ordering comparison
+/// In order to be able to use `Number` as a mapping key, NaN floating values wrapped in `Number`
+/// are equals to each other and are less then any other floating value.
+/// It is not the case for underlying `f64` values itself. See the `PartialEq` implementation.
 impl Ord for Number {
     fn cmp(&self, other: &Self) -> Ordering {
         self.partial_cmp(other).expect("Bug: Contract violation")


Test patch:
diff --git a/tests/floats.rs b/tests/floats.rs
new file mode 100644
index 00000000..ff192179
--- /dev/null
+++ b/tests/floats.rs
@@ -0,0 +1,8 @@
+use ron::de::from_str;
+
+#[test]
+fn test_inf_and_nan() {
+    assert_eq!(from_str("inf"), Ok(std::f64::INFINITY));
+    assert_eq!(from_str("-inf"), Ok(std::f64::NEG_INFINITY));
+    assert_eq!(from_str::<f64>("NaN").map(|n| n.is_nan()), Ok(true))
+}
\ No newline at end of file