Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can the compiler not optimize floating point addition with 0? [duplicate]

I have four identity functions which do essentially nothing. Only multiplication with 1 could be optimized by clang to a single ret statement.

float id0(float x) {
    return x + 1 - 1;
}

float id1(float x) {
    return x + 0;
}

float id2(float x) {
    return x * 2 / 2;
}

float id3(float x) {
    return x * 1;
}

And the following compiler output is: (clang 10, at -O3)

.LCPI0_0:
        .long   1065353216              # float 1
.LCPI0_1:
        .long   3212836864              # float -1
id0(float):                                # @id0(float)
        addss   xmm0, dword ptr [rip + .LCPI0_0]
        addss   xmm0, dword ptr [rip + .LCPI0_1]
        ret
id1(float):                                # @id1(float)
        xorps   xmm1, xmm1
        addss   xmm0, xmm1
        ret
.LCPI2_0:
        .long   1056964608              # float 0.5
id2(float):                                # @id2(float)
        addss   xmm0, xmm0
        mulss   xmm0, dword ptr [rip + .LCPI2_0]
        ret
id3(float):                                # @id3(float)
        ret

I can understand why id0 and id2 can't be optimized. They increase the value which could then turn into positive infinity and the second operation would not change it back.

But why can't id1 be optimized? Additon with infinity would yield infinity, addition with any regular number would yield that number and addition with NaN would yield NaN. So why is it not a "true" identity operation like * 1.

Example with Compiler Explorer

like image 256
Jan Schultke Avatar asked Jun 01 '20 09:06

Jan Schultke


3 Answers

IEEE 754 floating-point numbers have two zero values, one negative, one positive. When added together, the result is the positive one.

So id1(-0.f) is 0.f, not -0.f.
Note that id1(-0.f) == -0.f because 0.f == -0.f.

Demo

Also, note that compiling with -ffast-math in GCC does make the optimization and changes the result.

like image 171
Nelfeal Avatar answered Nov 14 '22 13:11

Nelfeal


"I have four identity functions which do essentially nothing."

That's not true.

For floating-point numbers x + 1 - 1 is not equal x + 0, it is equal (x + 1) - 1. So if you have e.g. a very small x then you will lose that very small portion in the x + 1 step, and the compiler can't know if that was your intent or not.

And in the case of x * 2 / 2, the x * 2 might not be exact either, due to floating-point precision, so you have a similar case here, the compiler does not know if you for some reason want to change the value of x in that manner.

So these would be equal:

float id0(float x) {
    return x + (1. - 1.);
}

float id1(float x) {
    return x + 0;
}

And these would be equal:

float id2(float x) {
    return x * (2. / 2.);
}

float id3(float x) {
    return x * 1;
}

The desired behavior could for sure be defined in another way. But as already mentioned by Nelfeal this optimization has to be explicitly activated using -ffast-math

Enable fast-math mode. This option lets the compiler make aggressive, potentially-lossy assumptions about floating-point math. These include:

  • Floating-point math obeys regular algebraic rules for real numbers (e.g. + and * are associative, x/y == x * (1/y), and (a + b) * c == a * c + b * c),
  • Operands to floating-point operations are not equal to NaN and Inf, and
  • +0 and -0 are interchangeable.

fast-math is for clang and gcc a collection of flags (here the one listed by clang):

  • -fno-honor-infinities
  • -fno-honor-nans
  • -fno-math-errno
  • -ffinite-math
  • -fassociative-math
  • -freciprocal-math
  • -fno-signed-zeros
  • -fno-trapping-math
  • -ffp-contract=fast
like image 44
t.niese Avatar answered Nov 14 '22 11:11

t.niese


Read the floating-number-gui.de web page, more about IEEE 754, the C11 standard n1570, the C++11 standard n3337.

float id1(float x) {
    return x + 0;
}

If x happens to be a signaling NaN, your id1 might even not return (and probably should not return).

If x is a quiet NaN, then id1(x) != x since NaN != NaN (at least NaN == NaN should be false).

In some cases, you want costly arbitrary precision arithmetic. Then consider using GMPlib.

PS. Floating point numbers can give you nightmares or a PhD, at your choice. They sometimes kill people or at least make huge financial disasters (e.g. a loss of several hundred millions US$ or €).

like image 34
Basile Starynkevitch Avatar answered Nov 14 '22 12:11

Basile Starynkevitch