Risk of Logical Operations on Floating-Point Variables in C++

By Stephen R. Davis

Round-off errors in floating-point computation can create havoc with logical operations in C++, so you must be careful performing logical operations on floating-point variables. Consider the following example:

float f1 = 10.0;
float f2 = f1 / 3;
bool b1 = (f1 == (f2 * 3.0));   // are these two equal?

Even though it’s obvious to us that f1 is equal to f2 times 3, the resulting value of b1 is not necessarily true. A floating-point variable cannot hold an unlimited number of significant digits. Thus, f2 is not equal to the number we’d call “three-and-a-third,” but rather to 3.3333…, stopping after some number of decimal places.

A float variable supports about 7 digits of accuracy while a double supports a skosh over 16 digits. These numbers are approximate because the computer is likely to generate a number like 3.3333347 due to vagaries in floating-point calculations.

Now, in pure math, the number of 3s after the decimal point is infinite, but no computer built can handle an infinite number of digits. So, after multiplying 3.3333 by 3, you get 9.9999 instead of the 10 you’d get if you multiplied “three-and-a-third” — in effect, a round-off error. Such small differences may be unnoticeable to a person but not to the computer. Equality means exactly that — exact equality.

Modern processors are sophisticated in performing such calculations. The processor may, in fact, accommodate the round-off error, but from inside C++, you can’t predict exactly what any given processor will do.

The safer comparison follows:

float f1 = 10.0;
float f2 = f1 / 3;
float f3 = f2 * 3.0;
float delta = f1 - f3;
bool bEqual = -0.0001 < delta && delta < 0.0001;

This comparison is true if f1 and f3 are within some small delta from each other, which should still be true even if you take some small round-off error into account.

The logical AND && and logical OR || operators in C++ perform what is called short-circuit evaluation. Consider the following:

condition1 && condition2

If condition1 is not true, the overall result is not true, no matter what the value of condition2. (For example, condition2 could be true or false without changing the result.) The same situation occurs in the following:

condition1 || condition2

If condition1 is true, the result is true, no matter what the value of condition2 is.

To save time, C++ doesn’t evaluate condition2 if it doesn’t need to. For example, in the expression condition1 && condition2, C++ doesn’t evaluate condition2 if condition1 is false. Likewise, in the expression condition1 || condition2, C++ doesn’t evaluate condition2 if condition1 is true. This is known as short-circuit evaluation.

Short-circuit evaluation may mean that condition2 is not evaluated even if that condition has side effects. Consider the following admittedly contrived code snippet:

int nArg1 = 1;
int nArg2 = 2;
int nArg3 = 3;
bool b = (nArg1 > nArg2) && (nArg2++ > nArg3);

The variable nArg2 is never incremented because the comparison nArg2++ > nArg3 is not performed. There’s no need because nArg1 > nArg2 already returned a false so the overall expression must be false.