Precision loss in Aeson decoding with large float values

I’m having trouble with Aeson and Yesod when handling large float values. I’m using Aeson/TH to auto-generate fromJSON/toJSON for an ADT. Here’s a simplified version of my code:

data Currency = USD Double
$(deriveJSON defaultOptions ''Currency)

-- In my handler
amount <- requireCheckJsonBody :: Handler Currency

The problem is when I pass a big number like USD 9876543210.99, the amount ends up with a value of 9,876,543,000.0. It seems to be rounding off the last few digits.

I think this might be happening because of a conversion from a string-like Scientific notation to a Double, which is losing precision.

Does anyone know how to keep the full precision when decoding these large numbers? I need to handle financial data accurately, so even small rounding errors can cause issues. Any suggestions would be really helpful!

I encountered a similar issue in a financial application I worked on. The solution we implemented was to use the Decimal type from the Data.Decimal package instead of Double. It’s specifically designed for arbitrary-precision decimal arithmetic, which is crucial for financial calculations.

Here’s how you could modify your code:

import Data.Decimal

data Currency = USD Decimal
$(deriveJSON defaultOptions ''Currency)

This approach ensures you maintain exact precision for your monetary values, avoiding any rounding errors. Remember to add the decimal package to your dependencies. It’s a bit more involved than using Scientific, but it’s worth it for financial data integrity.

hey, i’ve dealt with this before. aeson uses scientific notation internally, which is more precise than double. try changing your type to data Currency = USD Scientific and import Data.Scientific. this should preserve the full precision of your financial data. hope that helps!

I’ve run into this exact problem before in a banking system I worked on. We ended up using the fixed package, which provides fixed-point decimal arithmetic. It’s perfect for financial calculations where you need a specific number of decimal places.

Here’s what we did:

import Data.Fixed

data Currency = USD (Fixed E2)
$(deriveJSON defaultOptions ''Currency)

The E2 type parameter specifies two decimal places, which is standard for most currencies. This approach gives you exact decimal arithmetic without the potential overflow issues of Decimal.

One thing to watch out for: make sure your front-end is sending the numbers as strings to preserve precision. We had to modify our API to accept string representations of numbers for large values.

Also, consider using a custom ToJSON instance to ensure the output format meets your needs. It’s a bit more work, but it gives you full control over serialization.