GaloisInc / json

Haskell JSON library
Other
27 stars 10 forks source link

Double vs. Rational in JSValue #5

Closed dckc closed 8 years ago

dckc commented 8 years ago

I'm using JSON for some expected test results and it's not working for floats.

I printed out some details and got...

LexerTestCase {input = "0.91", expected = [(".float64.",JSRational False (91 % 100))]}
Right [(".float64.",JSRational False (8196551321814303 % 9007199254740992))]

I'm surprised to see that this package uses haskell Rational to represent numeric literals. My expected test results are parse as:

λ> decode "0.91" :: Result JSValue
Ok (JSRational False (91 % 100))

But JS numeric literals are floats, not rationals (details below). My actual test result is 0.91 :: Double. This package provides a JSON instance for Double, but it produces...

λ> toRational (0.91 :: Double)
8196551321814303 % 9007199254740992

which doesn't equal my expected results, so my test fails.

Details on JS Floats

This JSON package cites Standard ECMA-262 3rd Edition - December 1999. In section 7.8.3 Numeric Literals, we read

Once the exact MV for a numeric literal has been determined, it is then rounded to a value of the Number type.

and

4.3.20 Number Type The type Number is a set of values representing numbers. In ECMAScript, the set of values represents the double-precision 64-bit format IEEE 754 values including the special “Not-a-Number” (NaN) values, positive infinity, and negative infinity

yav commented 8 years ago

Hello,

using Rational ensures that the abstract syntax looses no precision in the representation. JSON is just a textual format for representing a certain class of structured values, and while it is often used in conjunction with (Java|ECMA)Script, we didn't want to limit the number representations to what's available there.

The issue that you are encountering is that 0.91 cannot be represented exactly as a IEEE 756 number. This means that there is simply no way to write it either as a Haskell Double or a Number in JavaScript---whenever you write 0.91 you get a number that is a little larger. This is why toRational (0.91 :: Double) gives you the odd fraction, and not 91 % 100 as you might expect.

I am not sure about the context of your tests, but if you are wanting to extract Doubles out of some JSON structure, you should be able to parse it out as such. For example:

> decode "[0.91,10]" :: Result [Double]
Ok [0.91,10.0]
dckc commented 8 years ago

As you suggest, I found a reasonably straightforward work-around:

round JSON numbers to nearest Double in expected results https://github.com/dckc/masque/commit/e1ab61872f274d325162620167b09bb9f0f47889