FirefoxMetzger FirefoxMetzger - 3 months ago 9
C++ Question

Why can't the VS 2015 compiler optimise a branch in an abs() implementation on float numbers?

__declspec(dllexport)
float foo(float x) {
return (x < 0) ? x * -1 : x;
}


This is a very naive implementation for calculating
abs(x)
where
x
is a
float
. I compiled this in Release mode and enabled all optimisations I could find. The resulting
asm
is:

; 4 : return (x < 0) ? x * -1 : x;

movss xmm1, DWORD PTR _x$[ebp]
xorps xmm0, xmm0
comiss xmm0, xmm1
jbe SHORT $LN3@foo
xorps xmm1, DWORD PTR __xmm@80000000800000008000000080000000
$LN3@foo:
movss DWORD PTR tv66[ebp], xmm1
fld DWORD PTR tv66[ebp]


As you can see this still contains the branch and the conditional jump. Yet a
float
is defined by the IEEE754 and thus I could change the implementation to simply set the sign bit to 0:

__declspec(dllexport)
float foo(float x) {
void* bar = &x;
__int32 y = ((*(__int32*)bar) & ~(1 << 31));
return *(float*)&y;
}


which does not jump and requires less commands:

; 3 : void* bar = &x;
; 4 : __int32 y = ((*(__int32*)bar) & ~(1 << 31));

mov eax, DWORD PTR _x$[ebp]
and eax, 2147483647 ; 7fffffffH
mov DWORD PTR _y$[ebp], eax

; 5 : return *(float*)&y;

fld DWORD PTR _y$[ebp]


I would have expected that there even exist specific commands for this action, but maybe this is only on very special architectures?

So what is the reason the compiler can't catch this optimization? Or am I making a mistake by doing this?

Answer

Because that would yield the wrong result for negative zero!

Negative zero is not smaller than zero, so its sign stays negative, rendering an elimination of the conditional branch invalid.

Consider using something like

copysign(x, 0.0);

instead.

Comments