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
Rounding Up and Down a Number C++
Using 'Std::Function<Void(...)>' to Call Non-Void Function
What Is Going on with Bitwise Operators and Integer Promotion
How to Use the Non-Default Constructor for a Member
How to Convert Char* to Lpcwstr
Trial-Division Code Runs 2X Faster as 32-Bit on Windows Than 64-Bit on Linux
How to Truncate a Floating Point Number After a Certain Number of Decimal Places (No Rounding)
Why Is the Empty Base Class Optimization (Ebo) Is Not Working in Msvc
How to Deploy a Qt Application on Windows
How Do Compilers Treat Variable Length Arrays
Behavior When Dereferencing the .End() of a Vector of Strings
Is Reading an Indeterminate Value Undefined Behavior
Scope VS Life of Variable in C
How to Read Files in Sequence from a Directory in Opencv
Random Output Different Between Implementations
Copyfileex with Progress Callback in Qt
Mesh Class Called with Default Constructor Not Working Opengl C++