Serde_json::to_string different behaviour for f32

Hi,

if i pass the value to json! macro via method, it prints the value with decimal points which the original data has not

fn main() {
    let val = 219.83;
    t(val);   
}

fn t(v: f32) {
    let j = serde_json::json!({"val":v});
    let s = serde_json::to_string(&j).unwrap();
    println!("{s}");
}

the output string is

{"val":219.8300018310547}

if i pass the value to json! macro no problem

fn main() {
    let val = 219.83;
    let j = serde_json::json!({"val":val});
    let s = serde_json::to_string(&j).unwrap();
    println!("{s}");
}
{"val":219.83}

It does. Read https://floating-point-gui.de.

I suspect that's because in the second let val = 219.83 the compiler will infer that val's type is f64 (float literals are f64 by default), but when you do t(val) it'll infer that you actually wanted a f32.

That means you are actually comparing the difference between json!({ "val": 219.83_f32 }) and json!({ "val": 219.83_f64 }).

3 Likes

same behaviour with

let val: f32 = 219.83;

so why it doesn't print it 219.8300018310547 in the second block

No, that's simply not true. @Michael-F-Bryan's answer is exactly correct.

serde_json = { version="1.0.108", features = ["arbitrary_precision"] }

What happens is that serde_json's Value type represents all floating point numbers as f64. Your f32 gets converted to f64.

When 219.83 is parsed as f32, it actually becomes the closest real number that can be represented in f32, which is 219.8300018310546875.

When 219.83 is parsed as f64, it represents 219.830000000000012505552149377763271331787109375. A different number.

Therefore 219.83_f32 can't be written as "219.83" because that would be a different number when parsed as f64.

What it gets written as is the shortest decimal notation that is closest to 219.8300018310546875 in f64, and that happens to be 219.8300018310547.

2 Likes

so it is impossible getting {"val":219.83} with f32 without arbitrary_precision feature

fn main() {
    let val: f32 = 219.83_f32;
    t(val);   
}

fn t(v: f32) {
    let j = serde_json::json!({"val":v});
    let s = serde_json::to_string(&j).unwrap();
    println!("{s}");
}
{"val":219.8300018310547}

arbitrary_precision won't change it to 219.83.
219.8300018310547 is a more accurate representation of 219.83_f32 than 219.83.
219.83 would be less accurate.

If the difference between 219.83 and 219.8300018310547 is important to you then perhaps you shouldn't be using f32. f32 is not capable of representing 219.83 exactly (and neither is f64).

2 Likes

To be more explicit, if your numeric types will have at most 2 decimal places (e.g. some currencies) then you can use an integer type to store the hundredths instead.

1 Like

Thank you @danjl1100,

I already do that