Skip to content

Commit

Permalink
Merge pull request #643 from dtolnay-contrib/serdenan
Browse files Browse the repository at this point in the history
Serialize NaN without sign (serde only)
  • Loading branch information
epage authored Oct 27, 2023
2 parents 447624e + 7b359a8 commit b0ebb16
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 12 deletions.
10 changes: 6 additions & 4 deletions crates/toml/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -921,12 +921,14 @@ impl ser::Serializer for ValueSerializer {
}

fn serialize_f32(self, value: f32) -> Result<Value, crate::ser::Error> {
// Preserve sign of NaN. The `as` produces a nondeterministic sign.
let sign = if value.is_sign_positive() { 1.0 } else { -1.0 };
self.serialize_f64((value as f64).copysign(sign))
self.serialize_f64(value as f64)
}

fn serialize_f64(self, value: f64) -> Result<Value, crate::ser::Error> {
fn serialize_f64(self, mut value: f64) -> Result<Value, crate::ser::Error> {
// Discard sign of NaN. See ValueSerializer::serialize_f64.
if value.is_nan() {
value = value.copysign(1.0);
}
Ok(Value::Float(value))
}

Expand Down
4 changes: 2 additions & 2 deletions crates/toml/tests/testsuite/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ macro_rules! float_inf_tests {
assert!(inf.sf5.is_nan());
assert!(inf.sf5.is_sign_positive());
assert!(inf.sf6.is_nan());
assert!(inf.sf6.is_sign_negative());
assert!(inf.sf6.is_sign_negative()); // NOTE: serializes to just `nan`

assert_eq!(inf.sf7, 0.0);
assert!(inf.sf7.is_sign_positive());
Expand All @@ -63,7 +63,7 @@ sf2 = inf
sf3 = -inf
sf4 = nan
sf5 = nan
sf6 = -nan
sf6 = nan
sf7 = 0.0
sf8 = -0.0
"
Expand Down
20 changes: 14 additions & 6 deletions crates/toml_edit/src/ser/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,20 @@ impl serde::ser::Serializer for ValueSerializer {
}

fn serialize_f32(self, v: f32) -> Result<Self::Ok, Self::Error> {
// Preserve sign of NaN. The `as` produces a nondeterministic sign.
let sign = if v.is_sign_positive() { 1.0 } else { -1.0 };
self.serialize_f64((v as f64).copysign(sign))
}

fn serialize_f64(self, v: f64) -> Result<Self::Ok, Self::Error> {
self.serialize_f64(v as f64)
}

fn serialize_f64(self, mut v: f64) -> Result<Self::Ok, Self::Error> {
// Discard sign of NaN when serialized using Serde.
//
// In all likelihood the sign of NaNs is not meaningful in the user's
// program. Ending up with `-nan` in the TOML document would usually be
// surprising and undesirable, when the sign of the NaN was not
// intentionally controlled by the caller, or may even be
// nondeterministic if it comes from arithmetic operations or a cast.
if v.is_nan() {
v = v.copysign(1.0);
}
Ok(v.into())
}

Expand Down
1 change: 1 addition & 0 deletions crates/toml_edit/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ impl From<i64> for Value {

impl From<f64> for Value {
fn from(f: f64) -> Self {
// Preserve sign of NaN. It may get written to TOML as `-nan`.
Value::Float(Formatted::new(f))
}
}
Expand Down
60 changes: 60 additions & 0 deletions crates/toml_edit/tests/testsuite/float.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use toml_edit::Document;

macro_rules! float_inf_tests {
($ty:ty) => {{
let document = r"
# infinity
sf1 = inf # positive infinity
sf2 = +inf # positive infinity
sf3 = -inf # negative infinity
# not a number
sf4 = nan # actual sNaN/qNaN encoding is implementation specific
sf5 = +nan # same as `nan`
sf6 = -nan # valid, actual encoding is implementation specific
# zero
sf7 = +0.0
sf8 = -0.0
";

let document = document.parse::<Document>().unwrap();
let float = |k| document[k].as_float().unwrap();

assert!(float("sf1").is_infinite());
assert!(float("sf1").is_sign_positive());
assert!(float("sf2").is_infinite());
assert!(float("sf2").is_sign_positive());
assert!(float("sf3").is_infinite());
assert!(float("sf3").is_sign_negative());

assert!(float("sf4").is_nan());
assert!(float("sf4").is_sign_positive());
assert!(float("sf5").is_nan());
assert!(float("sf5").is_sign_positive());
assert!(float("sf6").is_nan());
assert!(float("sf6").is_sign_negative());

assert_eq!(float("sf7"), 0.0);
assert!(float("sf7").is_sign_positive());
assert_eq!(float("sf8"), 0.0);
assert!(float("sf8").is_sign_negative());

let mut document = Document::new();
document["sf4"] = toml_edit::value(f64::NAN.copysign(1.0));
document["sf6"] = toml_edit::value(f64::NAN.copysign(-1.0));
assert_eq!(
document.to_string(),
"\
sf4 = nan
sf6 = -nan
"
);
}};
}

#[test]
fn test_float() {
float_inf_tests!(f32);
float_inf_tests!(f64);
}
1 change: 1 addition & 0 deletions crates/toml_edit/tests/testsuite/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
mod convert;
mod datetime;
mod edit;
mod float;
mod invalid;
mod parse;
mod stackoverflow;

0 comments on commit b0ebb16

Please sign in to comment.