There is nothing in the man-pages, that would suggest that -fno-signed-zeros
would imply -ffinite-math-only
:
-fno-signed-zeros
Allow optimizations for floating point arithmetic that ignore the signedness of zero. IEEE arithmetic specifies the behavior of distinct
+0.0
and-0.0
values, which then prohibits simplification of expressions such asx+0.0
or0.0*x
(even with-ffinite-math-only
). This option implies that the sign of a zero result isn't significant.The default is -fsigned-zeros.
However, there are observations which could be explained if it were the case. Problems in my code boil down to the following somewhat silly example:
#include <complex>
std::complex<double> mult(std::complex<double> c, double im){
std::complex<double> jomega(0.0, im);
return c*jomega;
}
The compiler would be tempted to optimize the multiplication c*=jomega
to something similar to c={-omega*c.imag(), omega*c.real()}
However, IEEE 754 compliance and at least the following corner cases prevent it:
A) signed zeros, e.g. omega=-0.0
, c={0.0, -0.0}
:
(c*jomega).real() = 0.0*0.0-(-0.0)*(-0.0) = 0.0
-c.imag()*omega = -(-0.0)*(-0.0) = -0.0 //different!
B) infinities, e.g. omega=0.0
, c={inf, 0.0}
:
(c*jomega).real() = inf*0.0-0.0*0.0 = nan
-c.imag()*omega = -(0.0)*(0.0) = -0.0 //different!
C) nan
s, e.g. omega=0.0
, c={inf, 0.0}
:
(c*jomega).real() = nan*0.0-0.0*0.0 = nan
-c.imag()*omega = -(0.0)*(0.0) = -0.0 //different!
That means, we have to use both, -ffinite-math-only
(for B and C) and -fno-signed-zeros
(for A), in order to allow the above optimization.
However, even with only -fno-signed-zeros
on, gcc performs the above optimization, if I understand the resulting assembler right (or see the listings below to see the effects):
mult(std::complex<double>, double):
mulsd %xmm2, %xmm1
movapd %xmm0, %xmm3
mulsd %xmm2, %xmm3
movapd %xmm1, %xmm0
movapd %xmm3, %xmm1
xorpd .LC0(%rip), %xmm0
ret
.LC0:
.long 0
.long -2147483648
.long 0
.long 0
My first tought was, that this could be a bug - but all recent gcc-versions I have at hand produce the same result, so I'm probably missing something.
Thus my question, why is gcc performing the above optimization only with -fno-signed-zeros
on and without -ffinite-math-only
?
Listings:
separate mult.cpp
to avoid funky precalculation during the compilation
#include <complex>
std::complex<double> mult(std::complex<double> c, double im){
std::complex<double> jomega(0.0, im);
return c*jomega;
}
main.cpp:
#include <complex>
#include <iostream>
#include <cmath>
std::complex<double> mult(std::complex<double> c, double im);
int main(){
//(-nan,-nan) expected:
std::cout<<"case INF: "<<mult(std::complex<double>(INFINITY,0.0),
0.0)<<"\n";
//(nan,nan) expected:
std::cout<<"case NAN: "<<mult(std::complex<double>(NAN,0.0), 0.0)<<"\n";
}
Compile and run:
>>> g++ main.cpp mult.cpp -O2 -fno-signed-zeros -o mult_test
>>> ./mult_test
case INF: (-0,-nan) //unexpected!
case NAN: (-0,nan) //unexpected!