mpaland/printf

Warning: comparing floating point with == or != is unsafe [-Wfloat-equal]

HarjitSi opened this issue ยท 4 comments

Compiling with GCC using -Wfloat-equal turned on, I get the following warnings:
printf.c:346:18: warning: comparing floating point with == or != is unsafe [-Wfloat-equal]
printf.c:363:20: warning: comparing floating point with == or != is unsafe [-Wfloat-equal]

The code does has a compare to 0.5

Any ideas on how to resolve this?
Thank you for creating this wonderful function!

The simplest way would probably be to rewrite it to use something like:

// presuming diff is positive
if ( diff > 0.5 ) {

} else if ( !(diff < 0.5) ) {
    // if not > and not <, then it must be ==
    // (but this will also happen if diff is NaN)
}

I am not sure how to check for NaN without including <math.h>. So to get an actual comparison, I believe you would have to do something like this:

bool is_zero_point_five(double num)
{
    // private union, used to get the
    // bitwise representation of the float
    union double_to_u32
    {
        uint64_t as_uint;
        double as_float;
    };

    union double_to_u32 zero_point_five = { .as_float = 0.5 };
    union double_to_u32 value = { .as_float = num };

    return value.as_uint == zero_point_five.as_uint;;
}

Basically, this uses a union to get an unsigned 64 bit representation of the double, and then compares the integers.

This gives no warnings and produces rather simple code in gcc. With optimizations enabled, GCC simply compares the value to 0x3FE0000000000000, which is the IEEE-754 representation of 0.5:

// gcc ARM 64
is_zero_point_five:
        fmov    x1, d0
        mov     x0, 4602678819172646912
        cmp     x1, x0
        cset    w0, eq
        ret

// gcc x86-64 
is_zero_point_five:
        movabs  rax, 4602678819172646912
        movq    rdx, xmm0
        cmp     rdx, rax
        sete    al
        ret

This can be generalized to:

static inline bool double_compare_exact(double a, double b)
{
    union double_to_u32 { uint64_t as_uint; double as_float; };

    union double_to_u32 aa = { .as_float = a };
    union double_to_u32 bb = { .as_float = b };

    return aa.as_uint == bb.as_uint;
}

Due to the fact that gcc must account for NaNs and perhaps other floating point exceptions, it cannot optimize !(x < 0.5) && !(x > 0.5) to the equivalent of x != 0.5. However, although the union implementation doesn't produce warnings, it presumes that sizeof(double) is 8 (which should be true on practically all platforms).

Hello @HarjitSi,

thanks a lot for bringing this problem to attention.
I'm aware of this "bug" and I don't like it either. Comparing floats to discrete numbers is never a sure/good thing.
The whole ftoa routine needs further testing and minor refactoring. This is still on the todo list.
Anyway, this is just a warning but if you need to be warning free, a construct of greater/smaller comparisons might help - as a quick fix.

@vgrudenic , thanks a lot for your suggestion.
Is double really and always 8 bytes on any platform?
I have to investigate this a little further...

Merry christmas! ๐ŸŽ„

Hi, merry christmas to you too! ๐ŸŽ„๐Ÿ˜ƒ

No, I believe the C standard says nothing about the size, although IEEE 754 / IEC 60559 specifies float and double as 32 and 64-bit.

Perhaps my comment was unnecessarily complicated, and the most reasonable option would be to suppress the warning in GCC? This should leave it to compiler to produce the correct code for a given platform, regardless of the size of floats:

// doesn't have to be a separate method
static inline bool double_are_equal(double a, double b)
{

// temporarily suppress "comparing float with == is unsafe"
// (we really want to check exactly against 0.5)
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wfloat-equal"
#endif

    return a == b;

#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

}