So I've always been told NEVER to do this, and this time I pose the question to you: why? I'm sure there is a very good reason, I simply do not know what it is. :-P
Because floats and doubles cannot accurately represent most base 10 real numbers.
This is how an IEEE-754 floating-point number works: it dedicates a bit for the sign, a few bits to store an exponent, and the rest for the actual fraction. This leads to numbers being represented in a form similar to 1.45 * 10^4
; except that instead of the base being 10, it's two.
Certain real decimal numbers cannot be represented exactly in base two. For instance, if you store 0.1 inside a double
, you'll actually get something like 0.0999999999999999996
, and software rounds it to an acceptable value. However, when dealing with money, those tiny digits lose more and more precision as you add them, divide them, multiply them, etc. This makes floats and doubles inadequate for dealing with money.
Floats and doubles are approximate. If you create a BigDecimal and pass a float into the constructor you see what the float actually equals:
groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375
this probably isn't how you want to represent $1.01.
The problem is that the IEEE spec doesn't have a way to exactly represent all fractions, some of them end up as repeating fractions so you end up with approximation errors. Since accountants like things to come out exactly to the penny, and customers will be annoyed if they pay their bill and after the payment is processed they owe .01 and they get charged a fee or can't close their account, it's better to use exact types like decimal (in C#) or java.lang.BigDecimal in Java.
From Bloch, J., Effective Java, 2nd ed, Item 48:
The
float
anddouble
types are particularly ill-suited for monetary calculations because it is impossible to represent 0.1 (or any other negative power of ten) as afloat
ordouble
exactly.For example, suppose you have $1.03 and you spend 42c. How much money do you have left?
System.out.println(1.03 - .42);
prints out
0.6100000000000001
.The right way to solve this problem is to use
BigDecimal
,int
orlong
for monetary calculations.
What about using a double, and storing the number of pennies, or some particular fraction of a cent? If all transactions involve an integer number of pennies (or whatever fraction is used), the value will be precise. If some transactions involve weird fractions (e.g. interest payments, discounts, etc.) it may not be practical to store the "true" value precisely in any type of container, so one will have to deal with rounding issues. How is 'double' worse than any other type?
I prefer using Integer or Long to represent currency. BigDecimal junks up the source code too much.
You just have to know that all your values are in cents. Or the lowest value of whatever currency you're using.