dropbox / json11

A tiny JSON library for C++11.
MIT License
2.55k stars 615 forks source link

double precision problem #31

Closed WitzHsiao closed 8 years ago

WitzHsiao commented 9 years ago

I use json.dump() for my result. And I got a problem, when I put double in the object. I got 150.00700000000001 when the double value is 150.0. How can I solve this problem? Thanks

artwyman commented 9 years ago

Can you share some code which demonstrates this? doubles are generally subject to rounding errors, though if you're dealing with an integral value, and no calculations other than putting it into a Json object I wouldn't expect such a difference.

artwyman commented 8 years ago

Can't investigate without further information.

0x17 commented 7 years ago

I'm having the same problem. It can be reproduced adding the following snippet in a GoogleTest case:

string err;
DataObject obj = json11::Json::parse("{\"num\": 200.1}", err);
ASSERT_TRUE(err.empty());
ASSERT_TRUE(obj["num"].is_number());
ASSERT_EQ(200.1, obj["num"].number_value());
string out = obj.dump();
ASSERT_EQ("{\"num\": 200.1}", out);

resulting in

Value of: out
  Actual: "{\"num\": 200.09999999999999}"
Expected: "{\"num\": 200.1}"

I narrowed it down to the strtod function call in "parse_number". It seems that both strtod and atof for "200.1" yield the double value 200.09999999... So essentially there is no bug in Json11, it is just a floating point precision problem.

artwyman commented 7 years ago

Yeah, I don't know what the specific details of what atof/strod might be doing, but I think this is likely an inherent problem with floating-point. Mathematically, 1/10 is not perfectly representable using powers of 2, so what we're seeing is probably slightly different rounding behaviors coming out of the implementation of strtod on input, vs. snprintf at output. In particular, I think difference between your "expected output" and the actual output seems to be how many digits of accuracy are requested at print time. I tried this code:

double d = 200.1;
printf("%.14lf\n", d);

and it printed "200.09999999999999", but without the ".14" it printed 200.100000, so clearly the printf family of functions are doing some rounding. Json11 requests maximal digit precision for accuracy purposes. That should mean that what you're seeing is probably a closer decimal representation of the binary floating-point number in the double, though there could be further rounding error the more conversions are done.

For any practical calculation purposes, the values should be equivalent. Json11 wasn't designed to guarantee perfect fidelity in converstions string -> Json -> string. If we wanted that we could cache the string representation of various sub-objects, in order to re-serialize them.