AntiPattern Name: FloatingPointFractions
Type: Design
Problem: We need to represent values which are fractions, but our language does not have a native type for them
Context: We need to store intervals of time, or currency. So we store "hours", or "dollars". Then someone says "hey", "our customer needs these values accurate to the quarter-hour", or "the cent". So rather than recode everything, the underlying datatype is changed to float or double.
Forces: Inertia, IllusionOfSimplicity
Supposed Solution: use floating point arithmetic
Resulting Context:
Bwahaha! You fool! How many times can a modern CPU commit a roundoff error in a single second? Have you ever seen code like this:
if(Math.abs(timeA-timeB) < 1e-6) { msg("you cannot have timeA = timeB"); } if(amtremaining < 2.9999) { msg("you have less than 3 dollars remaining"); }Say it until you get it: "the number in your head is almost never the number in a floating point store.".
best practice for time and currency
Integers are perfectly accurate.
For time and currency
Why not just have getRemainingTime() return an instance of a class in the time type hierarchy? So it can return a Minute, or a Second, or a Picosecond... and presumably your implementation of operation on Time subclasses handles conversions as appropriate. -- AdamBerger
There are 2 ways of thinking about a particular floating-point value:
As you might guess, the customer wanting the values accurate to the quarter-hour is a true story. Ten-minute intervals of an hour (1/6) does not have an accurate fp representation and you can't do computing with them unless you handle roundoff every second line.
The note about variable names, btw, is more important than it seems. Our job was redeveloping a previous system, and in that system time was displayed to the user as a decimal number of hours. So it was v. important to keep track of what the variables really contained.
Oh, and the number of hours displayed was one decimal digit (bangs head on desk). The previous system was riddled with bugs like "I had three hours credit. I did an hour, and then another hour, so I should still have an hour left - so why is it saying that I have less than an hour (.9) remaining?"
A difficulty with all this is that some databases do implement accurate fixed-point arithmetic NUMBER(9,2). If you pull back the values as java.math.BigDecimal, then everything can be done right - but naive coders just whack it in a double and expect it to work. It won't.
It is worth noting that "fixed point" is not the same as "floating point".
I observe, as an example, that there are examples in C where (a+b)+c != a+(b+c) Most programmers do not expect this behaviour, and therein lay many potential bugs.
An infamous mistake was made in Algol 68 to try to fix one aspect of this sort of thing; they thought it would be friendly to make A = B true if they differed by a small enough non-zero delta (e.g. if A = B + 0.0000001 then they're considered equal). Problem is that this meant that often A=B and B=C but A != C, which made things worse overall than the original issue -- the cure was worse than the disease. See SecondSystemEffect.
Quite simply, there are no true shortcuts to obviate numerical analysis, although certain good practices can help (e.g. computing with intervals that bracket the desired approximation).
("In C"? Do you mean "In every programming language that uses IeeeSevenFiftyFour" ?)
I can't believe no one has mentioned BankersRounding. This alone has single-handedly destroyed millions of man-hours across all languages. Try rounding 2.5 and 3.5 in you favorite language and notice the wacko results. I was praying C# would've implemented a better rounding system, but they didn't.
What wacko results? Perhaps I am getting too used to it rounding this way. Are you saying that BankersRounding *should* be the default? Or something else? (If so, what?)
In WyCash we eventually refactored all imprecise and rounded arithmetic out. What a relief. If we saw a disagreement in the 10th decimal place someone would say, "It must be rounding." I would say, "It can't be rounding, we don't round anything." Then we would get on with tracking down the problem. Our implementation used unlimited precision integers for most numbers, but sometimes stored prices or ratios as rational numbers (an unlimited numerator over an unlimited denominator). The cost was unmeasureable in speed or space, a fact that surprised me. It seems even computationally intense business applications simply don't do enough arithmetic to justify anything short of exact. -- WardCunningham
See also: