Float rounding (in JSON)

Steven Schveighoffer schveiguy at gmail.com
Thu Oct 13 19:27:22 UTC 2022


On 10/13/22 3:00 PM, Sergey wrote:
> I'm not a professional of IEEE 754, but just found this behavior at 
> rounding in comparison with other languages. I supose it happened 
> because in D float numbers parsed as double and have a full length of 
> double while rounding. But this is just doesn't match with behavior in 
> other languages.
> 
> I'm not sure if this is somehow connected with JSON realizations.

It doesn't look really that far off. You can't expect floating point 
parsing to be exact, as floating point does not perfectly represent 
decimal numbers, especially when you get down to the least significant bits.

> 
> Is it possible in D to have the same results as in others? Because 
> explicit formatting is not the answer, since length of rounding could be 
> different. That's why just specify "%.XXf" will not resolve the issue - 
> two last numbers have 14 and 15 positions after the dot.

It seems like you are looking to output a certain number of digits. If 
you limit the digits, you can get the outcome you desire.

But I want to point out something you may have missed:

> 
> **Code Python**
> ```python
> import json
> 
> str = '{ "f1": 43.476379000000065, "f2": 43.499718999999987, "f3": 
> 43.499718000000087, "f4": 43.418052999999986 }'
> print(json.loads(str))
> ```
> **Result**
> ```
> {'f1': 43.476379000000065, 'f2': 43.499718999999985, 'f3': 
> 43.49971800000009, 'f4': 43.418052999999986}
> ```

Let's line these up so we can read it easier

```
f1 in:  43.476379000000065
f1 out: 43.476379000000065
f2 in:  43.499718999999987
f2 out: 43.499718999999985
f3 in:  43.499718000000087
f3 out: 43.49971800000009
f4 in:  43.418052999999986
f4 out: 43.418052999999986
```

Note how f2 is a different output significantly than the input. This is 
an artifact of floating point parsing and the digits that are the most 
insignificant.

Also note that the omission of the 7 in f3 doesn't seem to have to do 
with rounding, because the digits are less than the original. If that 
digit were anywhere close to significant, you would have expected the 
digit to appear.

> 
> **Code Crystal**
> ```crystal
> require "json"
> 
> str = "{ \"f1\": 43.476379000000065, \"f2\": 43.499718999999987, \"f3\": 
> 43.499718000000087, \"f4\": 43.418052999999986 }"
> 
> puts JSON.parse(str)
> ```
> **Result**
> ```
> {"f1" => 43.476379000000065, "f2" => 43.499718999999985, "f3" => 
> 43.49971800000009, "f4" => 43.418052999999986}
> ```

Same here

> 
> **Code D**
> ```d
> import std;
> 
> void main() {
>      string s = `{ "f1": 43.476379000000065, "f2": 43.499718999999987, 
> "f3": 43.499718000000087, "f4": 43.418052999999986 }`;
>      JSONValue j = parseJSON(s);
>      writeln(j);
> }
> ```
> **Result**
> ```
> {"f1":43.4763790000000654,"f2":43.4997189999999847,"f3":43.4997180000000867,"f4":43.4180529999999862}
> ```

Let's look at D's representation:

```
f1 in:  43.476379000000065
f1 out: 43.4763790000000654
f2 in:  43.499718999999987
f2 out: 43.4997189999999847
f3 in:  43.499718000000087
f3 out: 43.4997180000000867
f4 in:  43.418052999999986
f4 out: 43.4180529999999862
```

Why does it print one more digit than the other languages? Because that 
must be the default for `writeln`. You can affect this by changing the 
number of digits printed. But probably not when printing an entire JSON 
structure.

But look also at f3, and how actually D is closer to the expected value 
than with the other languages.

If you want exact representation of data, parse it as a string instead 
of a double.

I'm assuming you are comparing for testing purposes? If you are, just 
realize you can never be accurate here. You just have to live with the 
difference. Typically when comparing floating point values, you use an 
epsilon to ensure that the floating point value is "close enough", you 
can't enforce exact representation.

-Steve


More information about the Digitalmars-d-learn mailing list