Comparing Floating Point Number to Zero

Comparing floating point number to zero

You are correct with your observation.

If x == 0.0, then abs(x) * epsilon is zero and you're testing whether abs(y) <= 0.0.

If y == 0.0 then you're testing abs(x) <= abs(x) * epsilon which means either epsilon >= 1 (it isn't) or x == 0.0.

So either is_equal(val, 0.0) or is_equal(0.0, val) would be pointless, and you could just say val == 0.0. If you want to only accept exactly +0.0 and -0.0.

The FAQ's recommendation in this case is of limited utility. There is no "one size fits all" floating-point comparison. You have to think about the semantics of your variables, the acceptable range of values, and the magnitude of error introduced by your computations. Even the FAQ mentions a caveat, saying this function is not usually a problem "when the magnitudes of x and y are significantly larger than epsilon, but your mileage may vary".

What is a standard way to compare float with zero?

To compare a floating-point value with 0, just compare it:

if (f == 0)
// whatever

There is nothing wrong with this comparison. If it doesn't do what you expect it's because the value of f is not what you thought it was. Its essentially the same problem as this:

int i = 1/3;
i *= 3;
if (i == 1)
// whatever

There's nothing wrong with that comparison, but the value of i is not 1. Almost all programmers understand the loss of precision with integer values; many don't understand it with floating-point values.

Using "nearly equal" instead of == is an advanced technique; it often leads to unexpected problems. For example, it is not transitive; that is, a nearly equals b and b nearly equals c does not mean that a nearly equals c.

Can you compare floating point values exactly to zero?

Even though 0 has an exact representation, you can't rely on the result of a calculation using floats to be exactly 0. As you noted, this is due to floating point calculation and conversion issues.

So, you should test for 0 against your tolerance epsilon.

Comparison to 0.0 with floating point values

It's perfectly correct in your case to use floating point equality == 0.0.

It perfectly fits the intention of the function (return some value or 0.0 if it fails). Using any other epsilon is somehow arbitrary and require the knowledge of the range of correct values. If ever something went to change that could well be the range of values rather than 0, so testing == 0.0 is not less future proof than other solutions IMO.

The only problem I see is that some compilers will warn about suspiscious usage of equality (-Wfloat-equal)... That's as usefull as warning about int a,b,c; ...; c=a+b; because such instruction might possibly lead to problem (integer overflow and undefined behaviour). Curiously, I never saw the second warning.

So if you want to make usage of -Wall -Werror compiler options future proof, you might encode failure differently (with a negative value for example) and test for foo < 0.0 - until someone discover that floating point inequality might require a tolerance too and declare the construct as suspiscious.

Why comparing a small floating-point number with zero yields random result?

Barring the undefined behavior which can be easily be fixed, you're seeing the effect of denormal numbers. They're extremely slow (see Why does changing 0.1f to 0 slow down performance by 10x?) so in modern FPUs there are usually denormals-are-zero (DAZ) and flush-to-zero (FTZ) flags to control the denormal behavior. When DAZ is set the denormals will compare equal to zero which is what you observed

Currently you'll need platform-specific code to disable it. Here's how it's done in x86:

#include <string.h>
#include <stdio.h>
#include <pmmintrin.h>

int main(void){
int i = 12;
float f;
memcpy(&f, &i, sizeof i);

_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
printf("%e %s 0\n", f, (f == 0) ? "=": "!=");

_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_OFF);
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_OFF);
printf("%e %s 0\n", f, (f == 0) ? "=": "!=");

return 0;
}

Output:

0.000000e+00 = 0
1.681558e-44 != 0

Demo on Godbolt

See also:

  • flush-to-zero behavior in floating-point arithmetic
  • Disabling denormal floats at the code level
  • Setting the FTZ and DAZ Flags

Compare a floating point number to zero

Because -0.0f also compares equal to 0.0f, as required by the IEEE-754 standard.

You could replace the call with a comparison against the integer representations of both -0 (0x80000000) and +0, if this is a major performance issue. (Or better still, mask the sign bit and compare against 0.0f).

Is it safe to check floating point values for equality to 0?

It is safe to expect that the comparison will return true if and only if the double variable has a value of exactly 0.0 (which in your original code snippet is, of course, the case). This is consistent with the semantics of the == operator. a == b means "a is equal to b".

It is not safe (because it is not correct) to expect that the result of some calculation will be zero in double (or more generally, floating point) arithmetics whenever the result of the same calculation in pure Mathematics is zero. This is because when calculations come into the ground, floating point precision error appears - a concept which does not exist in Real number arithmetics in Mathematics.

Floating point comparison with zero

Some functions, when implemented the obvious way with floating point representations, are ill-behaved at certain points. That's especially likely to be the case when the function has to be manually defined at a single point: When things go absolutely undefined at a point, it's likely that they're hanging on for dear life when they get close.

In this case, that's from the kappa denominator fighting the kappa negative exponent. Which one wins the battle is determined on a bit-by-bit basis, each one sometimes winning the "rounding to a stronger magnitude" contest.

There's a variety of approaches to fixing these sorts of problems, all of them designed on a case-by-case basis. One often-flawed but easy-to-implement approach is to switch to a better-behaved representation (say, the Taylor expansion with respect to kappa) near the problematic point. That'll introduce discontinuities at the boundaries; if necessary, you can try interpolating between the two.



Related Topics



Leave a reply



Submit