4

I understand that monetary values should be stored and processed as integers due to rounding error problems. That makes sense to me and I get it (I think).

But what about tax rates? Is there any reason a tax rate (not tax amount, tax rate, like 6.5 or 8.125) needs to be stored as an integer as opposed to a decimal?

And if I do store a tax rate as an integer, how do I apply the tax rate to the dollar amount in the transaction? If I do 10000 * 1.065 ($100.00 * 1.065) for a 6.5% tax rate, what would be the benefit of having stored that 6.5% in the database as 6500 as opposed to 6.500? I don't believe that multiplying or dividing a number by 100 one time is something that's susceptible to rounding errors.

Does it matter how I store tax rates?

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
Jason Swett
  • 43,526
  • 67
  • 220
  • 351
  • The problem arises with floating point arithmetic ([here is one explanation](https://docs.python.org/2/tutorial/floatingpoint.html)). If you're storing the tax rate as a *decimal* rather than a float, and ensuring that the result of your formulas is a *decimal* precision value, rather than a float, I don't know that there would be any rounding error. What environment are you doing this in? More info & use-case/examples and someone may be able to provide more guidance. – David Zemens Oct 02 '14 at 18:38

1 Answers1

3

First, it's not that you shouldn't use decimals. It's that you shouldn't use floating point where you want to have an exact value. Not all decimals are floating point; see the decimal datatype in C# or java.math.BigDecimal in Java.

Second, powers of 10 are especially susceptible to weird floating point issues because the way that floating point is implemented results in infinitely repeating decimals for division by powers of 10. See this question. Here's a trivial example:

groovy:000> f = 0.1F // make a floating point number
===> 0.1
groovy:000> f * 100 
===> 10.000000149011612

This happens because the representation of 0.1 is a repeating decimal that gets truncated. Here the REPL lied, what is really in f isn't 0.1, it's 0.100000001490116119384765625.

You could round this and go on, there's an argument for doing that:

To resolve this issue, you need to provide appropriate rounding. With money this is easy as you know how many decimal places are appropriate and unless you have $70 trillion you won't get a rounding error large enough you cannot correct it.

But using BigDecimals:

groovy:000> d = new BigDecimal("0.1")
===> 0.1
groovy:000> d * 100
===> 10.0

Use a fixed point decimal so you know exactly what number you have. If your language doesn't have fixed decimals (like Javascript), you may have to fall back on integers.

Community
  • 1
  • 1
Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
  • Got it. So would it be correct to say that for currency I always want to be multiplying fixed-point numbers by fixed-point numbers? – Jason Swett Oct 02 '14 at 18:46
  • And this is a totally separate question, but I'm now totally confused about why I seem to see so many people advocating the storage of currency values as "cents" instead of dollar amounts, e.g. 10000 instead of 100.00. – Jason Swett Oct 02 '14 at 18:47
  • @Jason: pretty much. there's an interesting article at http://vanillajava.blogspot.com/2011/08/double-your-money-again.html arguing the opposite. but you have to know what you're doing. – Nathan Hughes Oct 02 '14 at 18:47
  • @Jason: i don't know what the context of that is, but in javascript there is no fixed decimal so ints are easier. – Nathan Hughes Oct 02 '14 at 18:48