How does the C == operator decide whether or not two floating point values are equal?
Today I was tracking down why my program was getting some unexpected checksum-mismatch errors, in some code that I wrote that serializes and deserializes IEEE-754 floating-point values, in a format that includes a 32-bit checksum value (which is computed by running a CRC-type algorithm over the bytes of the floating-point array).
After a bit of head-scratching, I realized the problem was the 0.0f and -0.0f have different bit-patterns (0x00000000 vs 0x00000080 (little-endian), respectively), but they are considered equivalent by the C++ equality-operator. So, the checksum-mismatch errors happened because my checksum-calculating algorithm picked up the difference between those two bit-patterns, while certain other parts of my codebase (that use floating point equality testing, rather th开发者_高级运维an looking at the values byte-by-byte) did not make that distinction.
Okay, fair enough -- I should probably have known better than to do floating-point equality testing anyway.
But this got me thinking, are there other IEEE-754 floating point values that are considered equal (according to the C == operator) but have different bit-patterns? Or, to put it another way, how exactly does the == operator decide whether two floating-point values are equal? Newbie me though it was doing something like memcmp() on their bit-patterns, but clearly it's more nuanced than that.
Here's a code example of what I mean, in case I wasn't clear above.
#include <stdio.h>
static void PrintFloatBytes(const char * title, float f)
{
printf("Byte-representation of [%s] is: ", title);
const unsigned char * p = (const unsigned char *) &f;
for (int i=0; i<sizeof(f); i++) printf("%02x ", p[i]);
printf("\n");
}
int main(int argc, char ** argv)
{
const float pzero = -0.0f;
const float nzero = +0.0f;
PrintFloatBytes("pzero", pzero);
PrintFloatBytes("nzero", nzero);
printf("Is pzero equal to nzero? %s\n", (pzero==nzero)?"Yes":"No");
return 0;
}
It uses the IEEE-754 equality rules.
-0 == +0
NaN != NaN
exact comparison. That's why it's best to avoid == as a test on floats. It can lead to unexpected and subtle bugs.
A standard example is this code:
float f = 0.1f;
if((f*f) == 0.01f)
printf("0.1 squared is 0.01\n");
else
printf("Surprise!\n");
because 0.1 can't be represented precisely in binary (it's a repeating whatever the hell you call a fractional binary) 0.1*0.1
won't be exactly 0.01
-- and thus the equality test won't work.
Numerical analysts worry about this at length, but for a first approximation it's useful to define a value -- APL called it FUZZ -- which is how closely two floats need to come to be considered equal. So you might, for example, #define FUZZ 0.00001f
and test
float f = 0.1f;
if(abs((f*f)-0.01f) < FUZZ)
printf("0.1 squared is 0.01\n");
else
printf("Surprise!\n");
For Windows platforms, this link has:
- Divide by 0 produces +/- INF, except 0/0 which results in NaN.
- log of (+/-) 0 produces -INF. log of a negative value (other than -0) produces NaN.
- Reciprocal square root (rsq) or square root (sqrt) of a negative number produces NaN. The exception is -0; sqrt(-0) produces -0, and rsq(-0) produces -INF.
- INF - INF = NaN
- (+/-)INF / (+/-)INF = NaN
- (+/-)INF * 0 = NaN
- NaN (any OP) any-value = NaN
- The comparisons EQ, GT, GE, LT, and LE, when either or both operands is NaN returns FALSE.
- Comparisons ignore the sign of 0 (so +0 equals -0).
- The comparison NE, when either or both operands is NaN returns TRUE.
- Comparisons of any non-NaN value against +/- INF return the correct result.
精彩评论