This specification allows implementations to set limits on the range
and precision of numbers accepted. Since software that implements
IEEE 754 binary64 (double precision) numbers [IEEE754] is generally
available and widely used, good interoperability can be achieved by
implementations that expect no more precision or range than these
provide, in the sense that implementations will approximate JSON
numbers within the expected precision. A JSON number such as 1E400
or 3.141592653589793238462643383279 may indicate potential
interoperability problems, since it suggests that the software that
created it expects receiving software to have greater capabilities
for numeric magnitude and precision than is widely available.
Note that when such software is used, numbers that are integers and
are in the range [-(2**53)+1, (2**53)-1] are interoperable in the
sense that implementations will agree exactly on their numeric
values.
And yes, this is completely insane for a format that supposed to be specifically for serialization and interop. Needless to say, the industry has enthusiastically adopted it to the point where it became the standard.I miss XML these days. Sure, it was verbose and had a bunch of different and probably excessive numeric types defined for XML Schema... but at least they were well-defined (https://www.w3.org/TR/xmlschema-2/#built-in-datatypes). And, on the other hand, without a schema, all you had were strings. Either way, no mismatched expectations.
$ python3
Python 3.10.13 (main, Aug 24 2023, 12:59:26) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 100000.000000000017
100000.00000000001 Python 3.8.10 (default, Nov 22 2023, 10:22:35)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from decimal import Decimal
>>> Decimal('100000.000000000017')
Decimal('100000.000000000017')
For example: >>> import json
>>> json.loads('{"a": 100000.000000000017}')
{'a': 100000.00000000001}
>>> json.loads('{"a": 100000.000000000017}', parse_float=Decimal)
{'a': Decimal('100000.000000000017')}⸻
1. This is a bit on my mind lately as I discovered that LibraryThing’s sort by LoC classification seems to be broken so I exported my library (discovering that they export as ISO8859-1 with no option for UTF-8) and wrote a custom sorter for LOC classification codes for use in finally arranging the books on my shelves after my move last year.
>>> Decimal('100000.00000000000000000000017') + Decimal('1')
Decimal('100001.0000000000000000000002')Alright, if "you" have only ever used python. In C, for example, we have hexadecimal floating point literals that represent all floats and doubles exactly (including infinities and nans that make the json parser fail miserably).
How do you do that?
A couple of resources I found but which I’m not sure if are about exactly what you speak of
https://stackoverflow.com/questions/65480947/is-ieee-754-rep...
https://gcc.gnu.org/onlinedocs/gcc/Hex-Floats.html
Furthermore, what exactly do you mean by “all floats and doubles exactly”?
// define a floating-point literal in hex and print it in decimal
float x = 0x1p-8; // x = 1.0/256
printf("x = %g\n", x); // prints 0.00390625
// define a floating point literal in decimal and print it in various ways
float y = 0.3; // non-representable, rounded to closest float
printf("y = %g\n", y); // 0.3 (the %g format does some heuristics)
printf("y = %.10f\n", y); // 0.3000000119
printf("y = %.20f\n", y); // 0.30000001192092895508
printf("y = %a\n", f); // 0x1.333334p-2100000.000000000017
And then you print it.
Does it preserve the exact value?
If you print your variable with the %a format, then YES, the exact value is preserved and there is no loss of information. The problem is that the literal that you wrote cannot be represented exactly. But this is hardly a fault of the floats. Ints have exactly the same problem:
int x = 2.5; // x gets the value 2
int y = 7/3; // same thingAlthough it would be good to move in the direction of using a BigDecimal equivalent by default when ingesting unknown data.