Repository: serde-rs/json (language: rust)

Issue:
`Number` loses precision on f32s
Hi there, I've gone through a couple of issues but haven't found anything that quite matches my problem, so if this is a duplicate, feel free to close this and direct me elsewhere :)

The following small snippet replicates my issue:
```rs
use serde::{Deserialize, Serialize};
use serde_json;

#[derive(Deserialize, Serialize, Debug, Clone)]
struct Test(f32);

fn main() {
    let a = Test(5.55);
    let value = serde_json::to_value(a.clone()).unwrap();
    println!("{value:?}");
    let serialized = serde_json::to_string(&value).unwrap();
    println!("{serialized}");
    let serialized = serde_json::to_string(&a).unwrap();
    println!("{serialized}");
}

// Number(5.550000190734863)
// 5.550000190734863
// 5.55
```

I would expect the serialized values to be identical (and serialized to 5.55), but they aren't. This happens both with and without `arbitrary_precision` and `float_roundtrip` enabled. I would've expected this to be precisely 5.55, especially with `arbitrary_precision` enabled. When changing the type to an `f64`, it works as expected.


Discussion / hints:

Fix patch:
diff --git a/src/number.rs b/src/number.rs
index 21a76411c..5ecbde873 100644
--- a/src/number.rs
+++ b/src/number.rs
@@ -279,6 +279,35 @@ impl Number {
         }
     }
 
+    pub(crate) fn as_f32(&self) -> Option<f32> {
+        #[cfg(not(feature = "arbitrary_precision"))]
+        match self.n {
+            N::PosInt(n) => Some(n as f32),
+            N::NegInt(n) => Some(n as f32),
+            N::Float(n) => Some(n as f32),
+        }
+        #[cfg(feature = "arbitrary_precision")]
+        self.n.parse::<f32>().ok().filter(|float| float.is_finite())
+    }
+
+    pub(crate) fn from_f32(f: f32) -> Option<Number> {
+        if f.is_finite() {
+            let n = {
+                #[cfg(not(feature = "arbitrary_precision"))]
+                {
+                    N::Float(f as f64)
+                }
+                #[cfg(feature = "arbitrary_precision")]
+                {
+                    ryu::Buffer::new().format_finite(f).to_owned()
+                }
+            };
+            Some(Number { n })
+        } else {
+            None
+        }
+    }
+
     #[cfg(feature = "arbitrary_precision")]
     /// Not public API. Only tests use this.
     #[doc(hidden)]
diff --git a/src/value/from.rs b/src/value/from.rs
index c5a6a3960..462ad3f51 100644
--- a/src/value/from.rs
+++ b/src/value/from.rs
@@ -40,7 +40,7 @@ impl From<f32> for Value {
     /// let x: Value = f.into();
     /// ```
     fn from(f: f32) -> Self {
-        From::from(f as f64)
+        Number::from_f32(f).map_or(Value::Null, Value::Number)
     }
 }
 
diff --git a/src/value/partial_eq.rs b/src/value/partial_eq.rs
index b4ef84c4f..6b2e350b6 100644
--- a/src/value/partial_eq.rs
+++ b/src/value/partial_eq.rs
@@ -9,6 +9,13 @@ fn eq_u64(value: &Value, other: u64) -> bool {
     value.as_u64().map_or(false, |i| i == other)
 }
 
+fn eq_f32(value: &Value, other: f32) -> bool {
+    match value {
+        Value::Number(n) => n.as_f32().map_or(false, |i| i == other),
+        _ => false,
+    }
+}
+
 fn eq_f64(value: &Value, other: f64) -> bool {
     value.as_f64().map_or(false, |i| i == other)
 }
@@ -90,6 +97,7 @@ macro_rules! partialeq_numeric {
 partialeq_numeric! {
     eq_i64[i8 i16 i32 i64 isize]
     eq_u64[u8 u16 u32 u64 usize]
-    eq_f64[f32 f64]
+    eq_f32[f32]
+    eq_f64[f64]
     eq_bool[bool]
 }
diff --git a/src/value/ser.rs b/src/value/ser.rs
index a29814e92..875d22e24 100644
--- a/src/value/ser.rs
+++ b/src/value/ser.rs
@@ -1,6 +1,5 @@
 use crate::error::{Error, ErrorCode, Result};
 use crate::map::Map;
-use crate::number::Number;
 use crate::value::{to_value, Value};
 use alloc::borrow::ToOwned;
 use alloc::string::{String, ToString};
@@ -149,13 +148,13 @@ impl serde::Serializer for Serializer {
     }
 
     #[inline]
-    fn serialize_f32(self, value: f32) -> Result<Value> {
-        self.serialize_f64(value as f64)
+    fn serialize_f32(self, float: f32) -> Result<Value> {
+        Ok(Value::from(float))
     }
 
     #[inline]
-    fn serialize_f64(self, value: f64) -> Result<Value> {
-        Ok(Number::from_f64(value).map_or(Value::Null, Value::Number))
+    fn serialize_f64(self, float: f64) -> Result<Value> {
+        Ok(Value::from(float))
     }
 
     #[inline]


Test patch:
diff --git a/tests/regression/issue1004.rs b/tests/regression/issue1004.rs
new file mode 100644
index 000000000..3f5bd96aa
--- /dev/null
+++ b/tests/regression/issue1004.rs
@@ -0,0 +1,12 @@
+#![cfg(feature = "arbitrary_precision")]
+
+#[test]
+fn test() {
+    let float = 5.55f32;
+    let value = serde_json::to_value(&float).unwrap();
+    let json = serde_json::to_string(&value).unwrap();
+
+    // If the f32 were cast to f64 by Value before serialization, then this
+    // would incorrectly serialize as 5.550000190734863.
+    assert_eq!(json, "5.55");
+}