Floating Point Fractions

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 Trust me, I've been there.

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 Appropriate Usage: For quantities such as weight or length, the round-off error caused by picking the nearest floating-point number is insignificant compared to the normal errors in measurement. For calculations where tiny errors can rapidly build up, compute with intervals (pairs of floating-point numbers) that bracket the exact value(s). But anything that people want tallied with perfect accuracy (ie: most business values) cannot be held in a float or double. Applicable Positive Patterns:

There are 2 ways of thinking about a particular floating-point value: In your role as developer/designer, it's generally best to assume that *every* floating point variable contains a value plus a small error, and every calculation accumulates more error. Design your program to (hypothetically) still give the accurate-enough results even with this worst-case error injected at every step, and you will be pleasantly surprised when the program calculates results with even better accuracy (in some cases, it may calculate the exact result).

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:

View edit of May 8, 2014 or FindPage with title or text search