0

A few experiments with Data.Aeson.Types.Internal.Number

import Data.Aeson

10.4
-- 10.4

realToFrac 10.4
-- 10.4

Number (realToFrac 10.4)   -- <-- the problematic expression
-- Number 10.4000000000000003552713678800500929355621337890625

Number 10.4
-- Number 10.4

The code below uses the problematic expression in order to JSON-encode a Float value:

data Value =
    VInt Int
  | VFloat Float
  deriving Show

instance ToJSON Value where
    toJSON (VInt n)   = Number (fromIntegral n)
    toJSON (VFloat f) = Number (realToFrac f)

Which will output JSON with values like 10.4000000000000003552713678800500929355621337890625.

How comes there are so many trailing digits with Number (realToFrac 10.4) and how to address this problem?

Jivan
  • 21,522
  • 15
  • 80
  • 131
  • 1
    [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html), and also related (but in the other conversion direction), [Is floating point math broken?](https://stackoverflow.com/q/588004/791604) – Daniel Wagner Apr 27 '21 at 17:04
  • so I'm guessing from this article's first paragraph that the only real (pun intended) solution is to round the number before the final output? and also, that higher-level languages such as Python do that exact thing behind the scenes? – Jivan Apr 27 '21 at 17:08
  • 1
    Does this answer your question? [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) – Alexis King Apr 27 '21 at 18:01
  • @AlexisKing not really. I'm well aware of the underlying issue, however here the question was more about "concretely, how do we work around this in Haskell, in this specific scenario". Which the accepted answer perfectly addresses. Link is still useful, though, thanks for sharing. – Jivan Apr 27 '21 at 19:56

2 Answers2

2

For various reasons, there is no perfect solution. You might like fromFloatDigits -- it will do better with 10.4, but worse with some other numbers.

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • `fromFloatDigits` effectively does perform better on pretty much any number I could try, when it comes to numbers entered as two-digits precision floats. – Jivan Apr 27 '21 at 17:18
1

This general issue was discussed at length in https://github.com/haskell/aeson/issues/546. The discussion didn't really lead anywhere. If there's anything to take away from it, it's that numbers are basically always broken/ambiguous in the JSON world, and if you want to be precise you need to store your data in strings instead.

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • Thanks for pointing this. Main takeaway seems to be “don’t use JSON if you don’t want to play by its rules” :) – Jivan Apr 28 '21 at 11:13
  • Well, it could be argued the “rules of JSON” say “all numbers are 64-bit floats”. That would be clear-cut enough, and it would actually be usable quite well for lots of applications. But that's not how Aeson treats JSON, nor how Python does. – leftaroundabout Apr 28 '21 at 11:16