Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Floating-point zero in C implementations (IEEE 754 invariants?)

Tags:

I have a following C expression (variables are 32-bit floats)

float result = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0)

Assuming that x0==x1 and y0==y1, (and with == I mean binary representation identity), can I rely on the fact that the expression will necessarily evaluate to zero (as in, all bits of the float are set to 0)? In another words, can I assume that following invariants always hold?

memcmp(&a, &b, sizeof(float) == 0 => memcmp(a-b, (uint32_t)0, sizeof(float)) == 0
0*a == 0.0

It is safe to assume that all values are finite numbers (no INFINITY or NaN).

Edit: As pointed out in the answers, multiplications with 0 can produce signed zeros. Can I still rely on the fact that the result of the expression will be equal to 0.0 using FP-comparison rules, i.e.:

(result == 0.0) 

Edit 1: Replaced type casts by memcmp calls to illustrate the question better.

P.S. I am limiting my code to compliant C11 compilers only, in case it makes any difference. I am also willing to rely on STDC_IEC_559 support if that will help my case.

like image 345
MrMobster Avatar asked Jul 13 '16 11:07

MrMobster


1 Answers

Mentioning C11 just confuses your question because IEEE 754 is not required by any C standard.

That being said, just assuming IEEE 754 32 bit floating point numbers and making no assumptions on x2 and y2 (other than them not being infinite or NaN) you can't assume that all the bits of the result will be 0. IEEE 754 numbers have two zeroes, one negative and one positive and if the expression (y2 - y0) - (x2 - x0) is negative, the result of multiplying it with a zero will be a negative zero.

We can test it with this short example:

#include <stdio.h>
#include <stdint.h>

int
main(int argc, char **argv)
{
    union {
        float f;
        uint32_t i;
    } foo;

    float a = 0;
    float b = -1;

    foo.f = a * b;
    printf("0x%x\n", foo.i);

    return 0;
}

The result (notice no optimizations since I don't want the compiler to be clever):

$ cc -o foo foo.c && ./foo
0x80000000

Oh, I just noticed that the second part of your question which you called "in other words" isn't actually in other words because it's a different question.

To start with:

(*(uint32_t*)(&a) == *(uint32_t*)(&b))

isn't equivalent to a == b for floating point because -0 == 0. And with that, the other part of the assumption falls apart because -0 - 0 gives you -0. I'm not able to make any other subtraction of equal numbers generate a negative zero, but that doesn't mean it's not possible, I'm pretty sure that the standards don't enforce the same algorithm for subtraction on all implementations so a sign bit could sneak in there somehow.

like image 59
Art Avatar answered Sep 28 '22 04:09

Art