haskellyesodaeson

Aeson decode rounding by scientific notion


thanks for reading this , I've been struggled for a while I'm using Aeson/TH to auto generate the fromJSON/toJSON of a ADT. I'm using Yesod to accept data from HTTP call .

import Data.Aeson hiding (json)
import Language.Haskell.TH
import Data.Aeson.TH
import Data.Aeson.Types

data Money = USD Float
$(deriveJSON defaultOptions ''Money)

.... -- getting request from HTTP
_money <- requireCheckJsonBody :: Handler Money

The issue is that , if I pass a large nubmer in Money like USD 1157265240.03, but the _money will have float value of 1,157,265,300.0 which rounds 240 to 300.

this looks like caused by a conversion from String alike Scientific notation to a Float which will trancate the tail part of number.

Any idea how to bypass such conversion which cause loss of precision ? Thanks

enter image description here


Solution

  • The lack of precision has nothing to do with yesod or aeson, but instead with your choice of Float as a datatype. Type 1157265240.03 :: Float at the GHCi prompt, and you'll get 1.1572653e9 back. IEEE single-precision floats can't represent amounts more accurately than that once the magnitude gets that high. (Specifically, the only number between 1,157,265,152.00 and 1,157,265,408.00 that a single-precision float can hold is 1,157,265,280.00.) You need a different datatype. Aeson internally stores JSON numbers using the Scientific datatype, which is arbitrary precision. Another reasonable choice would be Centi from Data.Fixed.