aspose file tools*
The moose likes Java in General and the fly likes How NOT to convert dollar to cents Big Moose Saloon
  Search | Java FAQ | Recent Topics | Flagged Topics | Hot Topics | Zero Replies
Register / Login
JavaRanch » Java Forums » Java » Java in General
Bookmark "How NOT to convert dollar to cents" Watch "How NOT to convert dollar to cents" New topic
Author

How NOT to convert dollar to cents

Scott Selikoff
Saloon Keeper

Joined: Oct 23, 2005
Posts: 3697
    
    5

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.


My Blog: Down Home Country Coding with Scott Selikoff
David Newton
Author
Rancher

Joined: Sep 29, 2008
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

Joined: Mar 05, 2008
Posts: 2969
    
    9
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

Joined: Jul 08, 2003
Posts: 24166
    
  30

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!


[Jess in Action][AskingGoodQuestions]
Mike Simmons
Ranch Hand

Joined: Mar 05, 2008
Posts: 2969
    
    9
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

Joined: Sep 29, 2008
Posts: 12617

But! If we take all those rounding errors, shuffle it to an account...

PC Load Letter?
Mike Simmons
Ranch Hand

Joined: Mar 05, 2008
Posts: 2969
    
    9
Mmm... salami...
Jeanne Boyarsky
internet detective
Marshal

Joined: May 26, 2003
Posts: 29248
    
139

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...


[Blog] [JavaRanch FAQ] [How To Ask Questions The Smart Way] [Book Promos]
Blogging on Certs: SCEA Part 1, Part 2 & 3, Core Spring 3, OCAJP, OCPJP beta, TOGAF part 1 and part 2
Pat Farrell
Rancher

Joined: Aug 11, 2007
Posts: 4637
    
    5

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

Joined: Jul 08, 2003
Posts: 24166
    
  30

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

Joined: Mar 05, 2008
Posts: 2969
    
    9
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

Joined: Aug 11, 2007
Posts: 4637
    
    5

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

Joined: Aug 11, 2007
Posts: 4637
    
    5

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

Joined: Mar 05, 2008
Posts: 2969
    
    9
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

Joined: Aug 11, 2007
Posts: 4637
    
    5

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

Joined: Mar 05, 2008
Posts: 2969
    
    9
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

Joined: Mar 05, 2008
Posts: 2969
    
    9
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

Joined: Aug 11, 2007
Posts: 4637
    
    5

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

Joined: Mar 05, 2008
Posts: 2969
    
    9
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

Joined: Mar 05, 2008
Posts: 2969
    
    9
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

Joined: Mar 05, 2008
Posts: 2969
    
    9
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

Joined: Mar 05, 2008
Posts: 2969
    
    9
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

Joined: Mar 05, 2008
Posts: 2969
    
    9
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

Joined: Jul 22, 2000
Posts: 9043
    
  10
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.


JavaBeginnersFaq
"Yesterday is history, tomorrow is a mystery, and today is a gift; that's why they call it the present." Eleanor Roosevelt
Ulf Dittmer
Marshal

Joined: Mar 22, 2005
Posts: 39548
    
  27
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.


Ping & DNS - updated with new look and Ping home screen widget
Marilyn de Queiroz
Sheriff

Joined: Jul 22, 2000
Posts: 9043
    
  10
Interesting. Thanks, Ulf.
Pat Farrell
Rancher

Joined: Aug 11, 2007
Posts: 4637
    
    5

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.

 
I agree. Here's the link: http://aspose.com/file-tools
 
subject: How NOT to convert dollar to cents
 
Similar Threads
Vending Machine Project
Change Due/Tendered Program
Interface and Abstract Classes
Program: Change Due/Tendered
Vending Machine Project