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
```



Discussion:
@kvark Any idea how to handle this? Should we support infinity at all?
Right now, it serializes to `inf`.

Code to use for the integration test (in case this gets implemented):

```rust
use std::f64;

use ron::{de, ser};
use serde::*;

#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Test1 {
    field: f64,
}

#[test]
fn roundtrip_infinity() {
    let original = Test1 {
        field: f64::INFINITY,
    };

    let pretty: ser::PrettyConfig = Default::default();
    let serialized = ser::to_string_pretty(&original, pretty.clone()).unwrap();
    println!("serialized: {}", serialized);
    let duplicate: Test1 = de::from_str(&serialized).unwrap();
    assert_eq!(original, duplicate);
}

```
@torkleyy sounds like we need special ser/deser for "inf" and "-inf".
@kvark @torkleyy I'd suggest making sure that RON can handle all possible floating point values; I'm not too sure what your goals for RON are, but as an end user RON sounds like python pickle, but for rust.  In short, as an end user I kind of expect RON to be able to handle absolutely everything that rust can throw at it, unless there is an explicit marking that the thing can't be serialized for some reason (and I expect that to happen via serde, not via RON panicking).
@ckaran thanks for the advice. It sounds good to me to follow.



Patch:
diff --git a/src/de/mod.rs b/src/de/mod.rs
index ae0e706c5..cd4ad3cc5 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 00b3596e9..ddd50ca82 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 9f76ed143..5593a8729 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 cebac85b0..3167a36ce 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 000000000..ff192179d
--- /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