by Conrad Weisert
©2015 Information Disciplines, Inc., Chicago
May 26, 2015
NOTE: This document may be circulated or quoted from freely, as long as the copyright notice is included.
" . . don't even think about using doubles for any serious financial calculations, or you will quickly be faced with rounding errors, because floating-point arithmetic is not suited for financial applications." —InfoQ, March 3, 2015 | "For any calculation involving money or finances, the Decimal type should always be used. Only this type has the appropriate precision to avoid critical rounding errors."— C# 4.0 How-to, ISBN 0-672-35063-6, p. 78 |
That bit of misguided folklore has been circulating in various forms for a half-century, since the days of the first machines that offered built-in hardware floating-point. But we recall applications on second-generation computers^{1} that managed to yield penny accuracy with floating-point currency representations.
What such warnings should have advised was just that we should avoid fractional binary
scaling for quantities, such as money, that require exact decimal representatiion.
One-tenth (0.1) is a non terminating fraction in binary representation. If we represent a penny by
converting 0.01 to binary floating point and then convert that to an output display format we're likely
to be surprised and disappointed by $0.00999999999999999
.
Sure, that's a penny, too, but accountants will disapprove and customers will be confused.
Of course, binary representation (whether fixed- or floating-point) is perfectly accurate for integer quantities, as long as they don't overflow. All we have to do is to choose a penny (or whatever we want for our smallest increment) as our internal representation unit. Fortran programmers understood that in 1958, so we're perplexed by some of the strange advice being given to C++ and Java programmers in 2015.
Indeed, object oriented languages have greatly simplified programming of monetary manipulations. Programmers no longer need to keep track of numerical scaling or to code unnatural and unintuitive constants. That knowledge can easily be localized:
Now, if that works so well for floating-point internal representation it should also work for long (64-bit)^{2} integers, which are now available as very efficient native types in modern computers and programming languages. Although we haven't examined the code generated by popular compilers, it's reasonable to assume that addition and subtraction of Money objects having long internal representation will be at least as efficient as any alternative internal "decimal" representation. So should multiplication or division of a Money object by an integer.
What's the largest amount of money a 21st-century application is likely to have to represent. What's the smallest denomination? How many times does the smallest denomination divide the largest quantity?
To get penny accuracy in 64-bits we can represent quantities up to 2^{63}/100 dollars. That's more than $92 quadrillion. Despite inflation, we know of no modern application that needs to represent amounts of money beyond that gigantic limit.^{3} If our applications need half-cent accuracy, then we can limit money representation to $46 quadrillion, and for tenths of a cent to a still-comfortable $9.2 quadrillion.^{4}
All forms of magnetic and optical memory have become much less expensive in recent decades but they're still finite and not free. While the space consumed by a single Money quantity isn't critical, the impact of an array or file containing a hundred thousand of them is still significant.
The representation we proposed above consumes 64 bits. For representing unit prices in a supermarket inventory that may be viewed as extravagant, but for the grand total of the account balances in a large bank it's reasonable.
It can get worse. The built-in decimal type in C#, consuming 128 bits (16 bytes!) for each item, is being proposed as well-suited to money representation. It gets even worse than that. Java's BigDecimal library class combines wasteful storage with painfully slow execution.
How should a programmer choose among those expensive alternatives? Is there a cheaper option?
Well, we can certainly provide multiple object-oriented classes for different kinds of money. In addition to the Money class described above, we could provide SmallMoney with a 32-bit internal representation. And should the need ever arise (doubtful) we could provide HugeMoney perhaps using C#'s 16-byte decimal for the underlying value.
But doing that is complicated. The class definitions would need to support not only the usual operations on objects of each class, but also the meaningful conversions among them and mixed operations. Before undertaking that chore, we'll wait until someone demands it.
Experienced programmers may disagree about the relative merits of various internal representations but the one thing everyone should agree on is that all monetary data should be instances of a well-defined Money class. Declaring an amount of money as a raw decimal or BigDecimal object is as serious an error as declaring it double. A Money class, like the ones on this web site,^{5} must support legitimate operations and prevent illegitimate ones. That's what OOP is for.
Last modified June 6, 2015
Return to Technical articles
IDI Home Page