Win a copy of The Way of the Web Tester: A Beginner's Guide to Automating Tests this week in the Testing forum!

# How NOT to convert dollar to cents

Scott Selikoff
author
Saloon Keeper
Posts: 4028
18
Here's a question that came up on a production issue recently for converting dollars to whole number with cents as the unit.

Without running the following code, what would you expect the output to be?

Here, I'll even write it out in more detail:

Go run it, I bet the answer will surprise you. I'll wait to post the answer to why it doesn't work until people have had a chance to try it.

David Newton
Author
Rancher
Posts: 12617
I was just having a... discussion about this with some co-workers the other day; it was very irritating that nobody recognized this as a potential issue, and I got mad. If, however, you keep the cent amount as a double, I haven't been able to come up with an example that makes the point (and there are percentage-based fees, which is the usecase that concerns me).

Mike Simmons
Ranch Hand
Posts: 3090
14
David Newton wrote:If, however, you keep the cent amount as a double, I haven't been able to come up with an example that makes the point (and there are percentage-based fees, which is the usecase that concerns me).

I would add that if you do need to convert the double back to an int or long, using Math.round() is usually better than simply casting. Fixes the above problem just fine. Unfortunately it's all too easy for this sort of error to slip by unnoticed - I agree it can be a problem.

Ernest Friedman-Hill
author and iconoclast
Marshal
Posts: 24212
35
I knew this one right away, but my first impulse to "fix" it -- one I always stop myself before applying, but one that's in the back of my head from the "good old days", looks like this:

The real answer, I agree, is never to represent money using floating point!

Mike Simmons
Ranch Hand
Posts: 3090
14
Well, try to minimize the use of floating point, sure. But sometimes it enters into calculations whether you want it or not. E.g. with percentage-based fees as David mentioned, or if you need to pro-rate a shipping cost across two out of three items (i.e. multiplying by 2/3, or rather 2.0/3.0). In such cases it's nice to do proper rounding, rather than suffer Java's default behavior of truncation.

David Newton
Author
Rancher
Posts: 12617
But! If we take all those rounding errors, shuffle it to an account...

Mike Simmons
Ranch Hand
Posts: 3090
14
Mmm... salami...

Jeanne Boyarsky
author & internet detective
Marshal
Posts: 35095
380
David Newton wrote: If, however, you keep the cent amount as a double, I haven't been able to come up with an example that makes the point (and there are percentage-based fees, which is the usecase that concerns me).

The cent amount as a double meaning 123.45 or 12345 to mean \$123.45.

Also, did you try using really large numbers (from the times I've seen this issue, it comes up more frequently when you use trillions of dollars as the amount.) Of course, you may not deal with trillions of dollars in your domain...

Pat Farrell
Rancher
Posts: 4678
7
Using float or double for money is a sin. And it leads to madness. Don't do it.

Store the number of pennies and add the formatting on output.

Ernest Friedman-Hill
author and iconoclast
Marshal
Posts: 24212
35
Mike Simmons wrote:Well, try to minimize the use of floating point, sure. But sometimes it enters into calculations whether you want it or not.

Well, I have to admit I've never worked on financial software. But BigDecimal, maybe?

Mike Simmons
Ranch Hand
Posts: 3090
14
Jeanne Boyarsky wrote:The cent amount as a double meaning 123.45 or 12345 to mean \$123.45.

Using a double, I doubt it matters much - as long as you use it consistently. Either way, the double will give you 15-16 digits of accuracy for each number or operation. It doesn't really matter where the decimal is.

Jeanne Boyarsky wrote:Also, did you try using really large numbers (from the times I've seen this issue, it comes up more frequently when you use trillions of dollars as the amount.) Of course, you may not deal with trillions of dollars in your domain...
Yeah, most of us don't have that problem. :) But as \$1 trillion uses 15 digits (including the cents), you can expect to start having observable discrepancies if you're dealing with tens of trillions of dollars using doubles. Less than that though, and it's generally not an issue.

Of course, using a long instead gives more digits of accuracy (I think it's 18 digits or so, offhand), and using BigInteger or BigDecimal give essentially unlimited digits. I'm not saying these shouldn't be used - I'm just saying double can be used, for many domains, with no problems at all. Provided proper rounding is observed.

More generally though, I think it's useful to create an immutable wrapper class, say Money or Currency, that encapsulates a value in whatever format you want, and provides convenient methods to convert to a formatted string, an integer number of cents, a value multiplied by some ratio with proper rounding, or whatever else you need. This is beneficial for enforcing a single standard on how money is represented in your system, and helps prevent misunderstandings like whether your BigDecimal of 123 represents \$1.23 or \$123.00. Internally I would probably just use a BigInteger to represent cents, but most users of the class shouldn't need to care. And if you want to change the implementation later, you can.

Pat Farrell
Rancher
Posts: 4678
7
Mike Simmons wrote:rather than suffer Java's default behavior of truncation.

Its not just Java's rounding/truncation code. Floating point arithmetic is designed for engineering or statistical work, where getting three to six digits of precision is fine. If you are doing financial work, don't use float or double.

Its a moderate pain to implement a Money or Currency objects, but you let the class but then you can let the class do the parsing and presentation of values.

I don't know of any currency in the world that needs more than three digits to the right of the decimal place. Usually you can just use "pennies" in the local money format. Many currencies don't even have fractional values, for example the Yen or the old Italian Lira. For US, UK Pounds Sterling, and Euros, you can do fine with an implicit decimal point and store the whole number of pennies or pence.

When you are doing seriously big numbers, such as the US Federal Budget, you have to use something like BigDecimal.

Pat Farrell
Rancher
Posts: 4678
7
Mike Simmons wrote:Using a double, I doubt it matters much - as long as you use it consistently. Either way, the double will give you 15-16 digits of accuracy for each number or operation. It doesn't really matter where the decimal is.

This is incorrect. Double will give you about 15 digits of accuracy for some sets of numbers. It will not give you what you expect for many common cases. Specifically, there is no exact representation of the decimal number 0.1 in binary. None. Its trivial to write a small piece of code to put 0.1 in a double, and see how quickly the results are not what you want or expect:

It makes no real difference if you use double instead of float.

Do not use floating point representations for money. Its a sin.

Mike Simmons
Ranch Hand
Posts: 3090
14
Ernest Friedman-Hill wrote:
Mike Simmons wrote:Well, try to minimize the use of floating point, sure. But sometimes it enters into calculations whether you want it or not.

Well, I have to admit I've never worked on financial software. But BigDecimal, maybe?

Yes, BigDecimal helps in many cases. But there are still cases where we have to round something off, like 5% of 1.99 (should that be 0.10 or 0.09?), or multiplying by a ratio of 2/3. My point is that we need to be cognizant of rounding rules when we do this.

Pat wrote:Its not just Java's rounding/truncation code. Floating point arithmetic is designed for engineering or statistical work, where getting three to six digits of precision is fine. If you are doing financial work, don't use float or double.

Well with double (I never use float anymore) you can certainly get much more than three to six digits. I hear what you're saying, but to me the use of truncation rather than rounding is a much bigger source of problems than using double is.

Pat Farrell
Rancher
Posts: 4678
7
Mike Simmons wrote:Well with double (I never use float anymore) you can certainly get much more than three to six digits. I hear what you're saying, but to me the use of truncation rather than rounding is a much bigger source of problems than using double is.

No, you can't. Run the code in the post upthread that I posted.

It makes zero difference at even modest numbers of iterations even using double.

Its not truncation or rounding, its representation.

if you are not seeing the problem, you simply are not testing enough cases.

Mike Simmons
Ranch Hand
Posts: 3090
14
Pat Farrell wrote:
Mike Simmons wrote:Using a double, I doubt it matters much - as long as you use it consistently. Either way, the double will give you 15-16 digits of accuracy for each number or operation. It doesn't really matter where the decimal is.

This is incorrect. Double will give you about 15 digits of accuracy for some sets of numbers. It will not give you what you expect for many common cases. Specifically, there is no exact representation of the decimal number 0.1 in binary. None. Its trivial to write a small piece of code to put 0.1 in a double, and see how quickly the results are not what you want or expect:

Well, I'd say they are about what I'd expect - but probably not what the user wanted. Your code does demonstrate one big problem with floating-point primitives in Java which I'd neglected to mention, and that's that == becomes completely unreliable. So a new method of comparison is necessary. For example:

So yeah, it's a bit ugly, and this sort of thing can be a major gotcha in code with primitive floating point if you overlook it. But it is possible to handle.

Mike Simmons
Ranch Hand
Posts: 3090
14
And to reiterate an earlier point: I do think using BigInteger or BigDecimal is preferable in general. I'm just saying, double is capable of a lot more accuracy than people seem to give it credit for. And people who do use double need to be aware of where their pain points really are.

Pat Farrell
Rancher
Posts: 4678
7
Mike Simmons wrote:Well, I'd say they are about what I'd expect - but probably not what the user wanted. Your code does demonstrate one big problem with floating-point primitives in Java which I'd neglected to mention, and that's that == becomes completely unreliable. So a new method of comparison is necessary.

If you expected it, you are smart and insightful. Most programmers do not expect it. More importantly, no bosses expect it, and no auditors will accept it.

If you are using floating point for engineering or statistics, the "approximately equal" is a well understood problem.

I built some more generalized code from a posting by @Ernest F-H that is part of my open source library. Your code's use of " .000001" as a delta
is fine for money values, but is not valid in general.

Mike Simmons
Ranch Hand
Posts: 3090
14
The Ranch is acting up on me at the moment, blocking every attempted post. I will try breaking it up into smaller parts, as that seemed to work well in the past.

Mike Simmons
Ranch Hand
Posts: 3090
14
Pat Farrell wrote:If you expected it, you are smart and insightful.

Thanks. But you expected it too; that's why you wrote the code the way you did.

Put another way, I stand by my statement that double gives a lot more than 3-6 digits of accuracy. Unfortunately using == requires exact accuracy, which double cannot guarantee in general.

Mike Simmons
Ranch Hand
Posts: 3090
14
Pat Farrell wrote:Most programmers do not expect it.

This is probably quite true, unfortunately. I think most people who learned computing from a math/hard science/engineering background (like yourself, I gather) can grok it. It's standard stuff in a numerical methods course. But this sort of thing is probably not standard for many newer programmers, these days.

Pat Farrell wrote:If you are using floating point for engineering or statistics, the "approximately equal" is a well understood problem.

I would say it applies if you're using Java floating point primitives at all. That's why JUnit's assertEquals(double, double, String) is deprecated, after all, regardless of whether the user is doing engineering or statistics. They chose to use an absolute epsilon rather than a relative one, but either way, some sort of error tolerance is necessary.

Pat Farrell wrote:More importantly, no bosses expect it, and no auditors will accept it.

Well, I don't know about what sort of auditors you get. But in my experience, they look carefully at the final results, not the code that generates it. So it's very important to ensure that 1.9999999999 gets displayed as 2.00, for example. And is stored that way in the database. But whether it was represented before that as 1.9999999999, 2, or 2.0000000001 - mnh, usually that's not so important. To me, those really are the same thing (within the limits of the system), and it's the coder's responsibility to ensure they all get represented as 2.00 in the end result.

Mike Simmons
Ranch Hand
Posts: 3090
14
Pat Farrell wrote:Your code's use of " .000001" as a delta is fine for money values, but is not valid in general.

Well remember you used floats, which limited the precision possible. Using doubles it would have been more like .000000000000001. But in general, yes, there's always some limit to the accuracy of floating-point primitives, and for some applications (managing the US budget down to the penny, for example) that limit may be unacceptable.

So, why should anyone ever use floating points for money, in light of these limits? I can think of three possible reasons: (1) they can be substantially faster, (2) they use less memory, and (3) they're more readable. Of course on most modern systems, 1 and 2 are often irrelevant (the bottleneck is elsewhere), and 3 may be somewhat counterbalanced by the gotchas we've been discussing. Still, it's at least possible that some projects may deal with so much data, or have such serious performance restrictions, that it's worth using primitives rather than BigInteger or BigDecimal.

Anyway, good discussion. Cheers!

Mike Simmons
Ranch Hand
Posts: 3090
14
That did it, finally. This has been a recurring problem for me in the past - big posts can't get through, but small ones do. Just lately it's resurfaced. So far I seem to be the only user who experiences this behavior, where the chance of failure is directly, demonstrably tied to the length of the post. But if anyone else notices similar problems, please let me know.

Marilyn de Queiroz
Sheriff
Posts: 9067
12
Ernest Friedman-Hill wrote:Well, I have to admit I've never worked on financial software. But BigDecimal, maybe?

1.1 + 2.2 != 3.3 in my tests ... even with BigDecimal.

Ulf Dittmer
Rancher
Posts: 42968
73
Marilyn de Queiroz wrote:1.1 + 2.2 != 3.3 in my tests ... even with BigDecimal.

How are you constructing the BigDecimal objects?

will yield something inexact due to the doubles, while

will yield exactly 3.3.

Marilyn de Queiroz
Sheriff
Posts: 9067
12
Interesting. Thanks, Ulf.

Pat Farrell
Rancher
Posts: 4678
7
Yes, @ulf's example shows how trivially float/double can fail you. The constructors taking a value that is autoboxed to float, and gives the "wrong" answer.