What's the right way to handle monetary values in code?

5 points by count ↗ HN
I've seen a number of things saying to use integers of the smallest value (say, cents for USD) to avoid floating point rounding issues. How does that work when you have to do math on them - is it proper to just 'eat' or eliminate the remainders (say, a 33% discount on $1.00USD is how much off? How much off is a 33% discount on $.99?)

8 comments

[ 3.1 ms ] story [ 25.0 ms ] thread
Assuming truncating division:

Round down:

discount = 33 value / 100;

Round up:

discount = (33 value + 99) / 100;

Round to nearest:

discount = (33 value + 50) / 100;

But which method is appropriate for financial code?
I would use whichever errs in favor of the customer. Its only one cent, after all.
If full price is £9.99 and you've got it on offer at £5.00, that's less than 50% off. If you want to say you're offering it at 50% off, you need to price it at £4.99.

In banking, I believe the convention is to round to the nearest penny, and round half-pennies to the nearest even penny.

Oh, sorry. I would say that selecting the rounding policy is up to you. I'd agree with the poster that gives the fractional penny to the customer.
What you need is a fixed point decimal. I found this article asking the same question. http://stackoverflow.com/questions/1019939/ruby-on-rails-bes... Fixed point is what is supposed to be used for money. Databases currently have a "Money" type.
Yep this is the right way to go .. use the Money type when you can.

Just remember though, don't hard code 2 decimal places through your system. I have had to write financial software that's needed to be flexible enough to be accurate to 4 decimal places. Some clients can have strict requirements on financial software, make sure you ask all the right questions first:

- Do you have dollar discounts, or percentage discounts, or both?

- When you calculate tax, you should do it on a per-line basis (don't sum up the invoice and calculate tax on the end - some countries / states / whatever can have tax exempt items)

- When you calculate discount, calculate dollar value to N decimal places, then round to Y decimal places, then calculate tax on the rounded figure

All of this might seem like a nightmare for a few cents, but it really matters if you're dealing with many items (where small differences add up over many items) or you have to integrate with another third party system, and you might need to make sure your totals match theirs exactly (to the cent)

EDIT: Citing my sources ... I have worked on financial software that spans New Zealand, Australia and Global stuff too. I have had to integrate with (and learn intimately) 40+ accounting packages. All of them behave slightly differently in terms of rounding, but your best best is to -

- Match the system you're integrating with

- If you must 'lose' a cent, favour the customer (as the above comment says)

Godspeed :)

In Tarsnap I use a pair of 64-bit values, one for dollars and one for attodollars. I figure I don't mind losing a few attodollars to rounding, and I'm unlikely to ever have revenue exceeding 2^64 dollars.