See also FloatingPointFractions
Refers to the use of floating-point types (float or double in CeeLanguage
/etc.) to store currency values, especially whenever the IeeeSevenFiftyFour
floating point standard is used. As IEEE 754 is a binary (radix-2) standard; it cannot represent arbitrary decimal fractions exactly
. Only those fractions whose denominator is a power of 2 (1/2, 3/8, 23415/65536, etc.) can be represented exactly; all other rational fractions must use a repeating binary. 0.1 in decimal is 0.00011001100110011001100110011 in binary, with the "0011" repeated forever.
This can lead to all sorts of errors in programs which do financial analyses.
I have seen "decimal libraries" that seem better for currency than floating point. They perform the math more like we did in school. They are less efficient than floating point because the calculations are done in base 10 instead of the base 2 that machines prefer. But the added accuracy can often justify it.
Although some languages have decimal math built in, the add-on decimal libraries often use strings to store the numbers. This allows hundreds of places of precision if need be, although usually an upper limit of say 20 decimal places is selected as the (overridable) default.
About once or twice a year I encounter problems with floating point in calculating currency or business rates, and require ugly code to work around it. Being a few pennies off can cause significant accounting problems. Accountants cannot lose pennies. There have been scams where pennies were siphoned off of millions of accounts such that no one account lost more than a few cents, yet a crime involving many thousands of dollars was committed.
While this has been the topic of several movies, I don't know of an actual instance where this actually happened.
- I was just reminded of Richard Pryor in Superman III driving to his job in a new sports car, after precisely such a scam. :)
- OfficeSpaceMovie has the same scam (one of the characters even compares it to Superman III, but in the form I first heard - stealing the rounding errors off thousands of transactions)
has native monetary types, and is perhaps one of the reasons for its endurance in finance and accounting.
You have NotaNumber $ on Your account
Gaming systems, especially larger casino environments with thousands
of slot-type machines, provide endless amusement for math blunder watchers. Most of our "primitive" gadgets transmit meter readings using BCD (BinaryCodedDecimal
) and our dollar/cents computations use special math routines using BCD/business math. Iterative stuff. No rounding except as sanctioned by design.
In the newer (NewImproved?
) units, there is a temptation (an iterative temptation, actually) to "use modern floating point" for our accounting/reporting requirements. And two or three times a year we have to demonstrate the error-prone nature of that beast.
It gets better. Gaming meter values have one thing that arbitrary-precision decimal numbers don't: a rollover point (which also may vary from one value to the next), so that for example (250000.00 - 350000.00) gives 900000.00. It gets even more interesting when you're combining meter values with different rollover points. Finally, multiplication and division of two meter values is utterly meaningless. At least nobody I know of has tried to drag multiple currencies into it.
Solution: Use fix-point math, which boils down to simple integer math on cents (or whatever the smallest unit of the currency involved is). This way, everything is efficient, yet no penny is lost ever.
Nope. Fixed point math forces rounding (and often the wrong kind of rounding) on each calculation. Just because you can't shave a penny into quarter pennies doesn't mean your interest calculation should throw all quarter pennies away.
So what alternative is there?
Arbitrary precision decimal math. It's what banks and insurance companies use.
Let's say someone makes a single deposit of $1 000.01 into a 3.33% APY 1-year CD account.
(And to make the math simpler, let's say there's another three-hundred-million-dollar "internal account" at the bank that is used to make these interest payments).
(Would slight different starting numbers make the effect more dramatic?)
With floating-point math,
- I calculate $48.111 063 of interest for the year (Let's pretend this is exact)
- I subtract that quantity from one bank account, and add that same quantity to another bank account.
- $300 001 111.110 000 000... = $3 000 000.00 + $1 111.11 (before)
- $300 001 111.109 999 996... = 299 999 951.888 936 996... ... + $1 159.221 063 000... (after)
Somehow we've lost a fraction of a penny somewhere.
With fixed-point math:
- I calculate $48.11 of interest for the year (admittedly "throwing away" 0.106 or so pennies).
- I subtract that quantity from one bank account, and add that quantity to another bank account.
- $300 001 111.11 = $3 000 000.00 + $1 111.11 (before)
- $300 001 111.11 = $2 999 951.89 + $1 159.22 (after)
The fixed-point math is the only way I know to guarantee that the total value of 2 accounts doesn't change when I move any quantity from one to the other.
Please tell me about any alternative.
One approach that seems to work fairly well for non-large floating-point numbers is to round after each and every calculation. This way, errors don't accumulate. It still tends to have problems if you do division. I've seen fudge factors added to division results to make sure rounding near 50-cent fractions happens as expected.
- this is a problem that the BankingIndustry? has had forever. They have formally defined rules dictating exactly what is done when the it's time for the bank to pay its quarterly interest . You don't address the issue by going "meh - floating point is accurate enough". Not when you are working with OtherPeoplesMoney?.
x = getNumberFoo()
y = getNumberBar()
a = x + y
a = round(a, 2) // round to pennies
b = a * 1.10
b = round(b, 2)
c = b / 5.0
c = c + 0.00000001 // fudge factor
c = round(c, 2)
- this is a horrible kludge. At the very, very least, this should be addressed with a "add and round" method, or a Currency type that explicitly OperatorOverloading the '+' operator.
A good article on this and related subjects is at http://www.codeproject.com/dotnet/ExtremeFloatingPoint1.asp?df=100&forumid=172674&exp=0&select=1121627
-centric, but the principles are applicable elsewhere.
's rational numbers seem like a good solution to the accuracy problem.
-- Excel uses floating point currency.
-- Foreign Exchange currency conversions are specified by explicit conventions to use Decimal Fixed Point Arithmetic (not integer arithmetic). This ensures that all rounding errors are consistent. The 8 place decimals either derive from paper calculations or some well-known mini computer system. However, most people just use Excel. This ensures that all rounding errors are consistent...
Reminds me of Consistent Mounds of Muck (CmmAcronymAlternatives)