Skip to content

Commit a8fa998

Browse files
authored
fix: reject -0.0 (#65)
For IEEE 754 floats `-0.0` is equal to `0.0`. It makes sense to have only a single representation for the same numbers, hence always encode it as `0.0`, i.e. `0x0000000000000000`.
1 parent 4e0fa1d commit a8fa998

5 files changed

Lines changed: 59 additions & 5 deletions

File tree

src/de.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,12 @@ impl<'de, R: dec::Read<'de>> serde::Deserializer<'de> for &mut Deserializer<R> {
425425
return Err(DecodeError::Mismatch { name, found: byte });
426426
}
427427

428+
// DAG-CBOR requires -0.0 to be encoded as 0.0.
429+
#[cfg(not(feature = "less-strict-decoding"))]
430+
if value == 0.0 && value.is_sign_negative() {
431+
return Err(DecodeError::Mismatch { name, found: byte });
432+
}
433+
428434
let f32_value = value as f32;
429435

430436
// Check if conversion causes overflow to infinity
@@ -461,6 +467,11 @@ impl<'de, R: dec::Read<'de>> serde::Deserializer<'de> for &mut Deserializer<R> {
461467
if !value.is_finite() {
462468
return Err(DecodeError::Mismatch { name, found: byte });
463469
}
470+
// DAG-CBOR requires -0.0 to be encoded as 0.0.
471+
#[cfg(not(feature = "less-strict-decoding"))]
472+
if value == 0.0 && value.is_sign_negative() {
473+
return Err(DecodeError::Mismatch { name, found: byte });
474+
}
464475
visitor.visit_f64(value)
465476
}
466477

src/ser.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ impl<'a, W: enc::Write> serde::Serializer for &'a mut Serializer<W> {
137137
"Float must be a finite number, not Infinity or NaN".into(),
138138
))
139139
} else {
140+
// -0.0 and 0.0 are equal, always encode as 0.0 (0x0000000000000000).
141+
let v = if v == -0.0 { 0.0 } else { v };
140142
v.encode(&mut self.writer)?;
141143
Ok(())
142144
}

tests/de.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,37 @@ fn test_float() {
184184
assert_eq!(ipld.unwrap(), Ipld::Float(100000.0));
185185
}
186186

187+
#[test]
188+
fn test_float_negative_zero() {
189+
// -0.0 (0x8000000000000000) must be encoded as 0.0, so decoding it is only allowed in the less
190+
// strict mode.
191+
let bytes = [0xfb, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
192+
193+
let result: Result<f64, _> = de::from_slice(&bytes);
194+
#[cfg(not(feature = "less-strict-decoding"))]
195+
assert!(matches!(
196+
result.unwrap_err(),
197+
DecodeError::Mismatch {
198+
name: "f64",
199+
found: 251
200+
}
201+
));
202+
#[cfg(feature = "less-strict-decoding")]
203+
assert_eq!(result.unwrap().to_bits(), (-0.0f64).to_bits());
204+
205+
let result_f32: Result<f32, _> = de::from_slice(&bytes);
206+
#[cfg(not(feature = "less-strict-decoding"))]
207+
assert!(matches!(
208+
result_f32.unwrap_err(),
209+
DecodeError::Mismatch {
210+
name: "f32",
211+
found: 251
212+
}
213+
));
214+
#[cfg(feature = "less-strict-decoding")]
215+
assert_eq!(result_f32.unwrap().to_bits(), (-0.0f32).to_bits());
216+
}
217+
187218
#[test]
188219
fn test_float_encoded_as_non_f64_ipld() {
189220
// Supporting f16 would need extra work and was never supported. Hence they even fail in less

tests/ser.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,18 @@ fn test_f32() {
5656
assert_eq!(vec, b"\xfb\x40\xaf\x41\x00\x00\x00\x00\x00");
5757
}
5858

59+
#[test]
60+
fn test_negative_zero() {
61+
// -0.0 and 0.0 are equal, -0.0 is encoded as 0.0 (0x0000000000000000).
62+
let neg = to_vec(&-0.0f64).unwrap();
63+
assert_eq!(neg, b"\xfb\x00\x00\x00\x00\x00\x00\x00\x00");
64+
assert_eq!(neg, to_vec(&0.0f64).unwrap());
65+
66+
// The same is true when serializing an f32.
67+
let neg_f32 = to_vec(&-0.0f32).unwrap();
68+
assert_eq!(neg_f32, b"\xfb\x00\x00\x00\x00\x00\x00\x00\x00");
69+
}
70+
5971
#[test]
6072
fn test_infinity() {
6173
let vec = to_vec(&f32::INFINITY);

tests/std_types.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,6 @@ fn test_f32_roundtrip() {
175175
// Test various f32 values
176176
let test_values = vec![
177177
0.0f32,
178-
-0.0f32,
179178
1.0f32,
180179
-1.0f32,
181180
1.5f32,
@@ -245,13 +244,12 @@ fn test_f32_strict_precision_rejection() {
245244
assert!(decoded.is_ok());
246245
assert_eq!(decoded.unwrap(), original);
247246

248-
// Negative zero should preserve sign bit and work
247+
// Negative zero is encoded as positive zero (0x0000000000000000).
249248
let neg_zero_f64 = -0.0f64;
250249
let encoded = to_vec(&neg_zero_f64).expect("encoding should succeed");
250+
assert_eq!(encoded, to_binary("fb0000000000000000"));
251251
let result: Result<f32, _> = from_slice(&encoded);
252252
assert!(result.is_ok());
253253
let decoded_f32 = result.unwrap();
254-
assert_eq!(decoded_f32, -0.0f32);
255-
// Check that sign bit is preserved
256-
assert_eq!(decoded_f32.to_bits(), (-0.0f32).to_bits());
254+
assert_eq!(decoded_f32.to_bits(), (0.0f32).to_bits());
257255
}

0 commit comments

Comments
 (0)