From 595c2cd4490c00aacc01ad92234cfba9a3bd7e1f Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Sat, 20 Jan 2018 14:41:42 -0500 Subject: [PATCH] Improved serialization/deserialization for floating point numbers. - deserialize/serialize inf/-inf/nan - always output decimal point for truncated floating points --- Cargo.toml | 1 + src/de.rs | 11 +++++++++++ src/lib.rs | 5 +++-- src/ser.rs | 13 +++++++++++-- tests/test_serde.rs | 24 ++++++++++++++++++++++++ 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6a775179..400c854d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ readme = "README.md" keywords = ["yaml", "serde"] [dependencies] +dtoa = "0.4" linked-hash-map = "0.5" num-traits = "0.1.37" serde = "1.0" diff --git a/src/de.rs b/src/de.rs index a5dc18d0..82ca8ffb 100644 --- a/src/de.rs +++ b/src/de.rs @@ -11,6 +11,7 @@ //! This module provides YAML deserialization with the type `Deserializer`. use std::collections::BTreeMap; +use std::f64; use std::fmt; use std::io; use std::str; @@ -490,6 +491,16 @@ fn visit_untagged_str<'de, V>(visitor: V, v: &str) -> Result if let Ok(n) = v.parse() { return visitor.visit_i64(n); } + match v.trim_left_matches('+') { + ".inf" | ".Inf" | ".INF" => return visitor.visit_f64(f64::INFINITY), + _ => (), + } + if v == "-.inf" || v == "-.Inf" || v == "-.INF" { + return visitor.visit_f64(f64::NEG_INFINITY); + } + if v == ".nan" || v == ".NaN" || v == ".NAN" { + return visitor.visit_f64(f64::NAN); + } if let Ok(n) = v.parse() { return visitor.visit_f64(n); } diff --git a/src/lib.rs b/src/lib.rs index 0efb2b38..2d9863ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,7 @@ //! //! // Serialize it to a YAML string. //! let s = serde_yaml::to_string(&map).unwrap(); -//! assert_eq!(s, "---\nx: 1\n\"y\": 2"); +//! assert_eq!(s, "---\nx: 1.0\n\"y\": 2.0"); //! //! // Deserialize it back to a Rust type. //! let deserialized_map: BTreeMap = serde_yaml::from_str(&s).unwrap(); @@ -53,7 +53,7 @@ //! let point = Point { x: 1.0, y: 2.0 }; //! //! let s = serde_yaml::to_string(&point).unwrap(); -//! assert_eq!(s, "---\nx: 1\n\"y\": 2"); +//! assert_eq!(s, "---\nx: 1.0\n\"y\": 2.0"); //! //! let deserialized_point: Point = serde_yaml::from_str(&s).unwrap(); //! assert_eq!(point, deserialized_point); @@ -78,6 +78,7 @@ empty_enum, ))] +extern crate dtoa; extern crate linked_hash_map; extern crate num_traits; #[macro_use] diff --git a/src/ser.rs b/src/ser.rs index 4ca67f60..61552658 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -10,7 +10,7 @@ //! //! This module provides YAML serialization with the type `Serializer`. -use std::{fmt, io}; +use std::{fmt, io, num, str}; use yaml_rust::{yaml, Yaml, YamlEmitter}; @@ -73,7 +73,16 @@ impl ser::Serializer for Serializer { } fn serialize_f64(self, v: f64) -> Result { - Ok(Yaml::Real(v.to_string())) + Ok(Yaml::Real(match v.classify() { + num::FpCategory::Infinite if v.is_sign_positive() => ".inf".into(), + num::FpCategory::Infinite => "-.inf".into(), + num::FpCategory::Nan => ".nan".into(), + _ => { + let mut buf = vec![]; + ::dtoa::write(&mut buf, v).unwrap(); + ::std::str::from_utf8(&buf).unwrap().into() + } + })) } fn serialize_char(self, value: char) -> Result { diff --git a/tests/test_serde.rs b/tests/test_serde.rs index f37e59f9..0d9f811c 100644 --- a/tests/test_serde.rs +++ b/tests/test_serde.rs @@ -17,6 +17,7 @@ extern crate serde_yaml; extern crate unindent; use unindent::unindent; +use std::f64; use std::fmt::Debug; use std::collections::BTreeMap; @@ -73,6 +74,29 @@ fn test_float() { --- 25.6"); test_serde(&thing, &yaml); + + let thing = 25.; + let yaml = unindent(" + --- + 25.0"); + test_serde(&thing, &yaml); + + let thing = f64::INFINITY; + let yaml = unindent(" + --- + .inf"); + test_serde(&thing, &yaml); + + let thing = f64::NEG_INFINITY; + let yaml = unindent(" + --- + -.inf"); + test_serde(&thing, &yaml); + + let float: f64 = serde_yaml::from_str(&unindent(" + --- + .nan")).unwrap(); + assert!(float.is_nan()); } #[test]