为什么仅 -fno-signed-0 就可以实现优化,而似乎也需要 -ffinite-math-only (gcc)
Why does -fno-signed-zeros alone enable optimization, for which seemingly also -ffinite-math-only is needed (gcc)
手册页中没有任何内容表明-fno-signed-zeros
暗示-ffinite-math-only
:
-FNO 符号零
允许对忽略零符号的浮点算术进行优化。IEEE 算术指定了不同行为
+0.0
并-0.0
值,然后禁止简化表达式,例如x+0.0
或0.0*x
(即使使用-ffinite-math-only
)。 此选项意味着零结果的符号不显著。默认值为 -fsigned-zero。
但是,如果情况确实如此,可以解释一些意见。我的代码中的问题归结为以下有点愚蠢的示例:
#include <complex>
std::complex<double> mult(std::complex<double> c, double im){
std::complex<double> jomega(0.0, im);
return c*jomega;
}
编译器会倾向于将乘法c*=jomega
优化为类似于c={-omega*c.imag(), omega*c.real()}
但是,IEEE 754 合规性和至少以下极端情况会阻止它:
A) 有符号零,例如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) 无穷大,例如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,例如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!
这意味着,我们必须同时使用-ffinite-math-only
(对于 B 和 C)和-fno-signed-zeros
(对于 A),以便进行上述优化。
但是,即使只打开了-fno-signed-zeros
,如果我正确理解生成的汇编程序,gcc 也会执行上述优化(或查看下面的列表以查看效果):
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
我的第一个问题是,这可能是一个错误 - 但我手头的所有最近的 gcc 版本都会产生相同的结果,所以我可能错过了一些东西。
因此,我的问题是,为什么 gcc 仅在打开-fno-signed-zeros
和没有-ffinite-math-only
的情况下执行上述优化?
清单:
单独的mult.cpp
以避免在编译过程中进行时髦的预计算
#include <complex>
std::complex<double> mult(std::complex<double> c, double im){
std::complex<double> jomega(0.0, im);
return c*jomega;
}
主.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";
}
编译并运行:
>>> g++ main.cpp mult.cpp -O2 -fno-signed-zeros -o mult_test
>>> ./mult_test
case INF: (-0,-nan) //unexpected!
case NAN: (-0,nan) //unexpected!
我这边是一个误解,认为复数乘法的定义与在学校学习的方式相同。
基本上,C++标准与复杂的乘法无关,因此可能必须参考C标准。仅从C99开始,复数是标准(附录G)的一部分,该标准尚未唯一定义复数乘法的所有结果。
最重要的定义是:
- 当两个部分都为零时,复数为零(
0.0
或-0.0
)。 - 当两个部分都是有限的并且不
nans
时,复数是有限的。 - 当实数或虚部(或两者兼而有之)
inf
或-inf
时,复数是无限的(即使另一个是nan
)。
它没有定义什么是复数nan
,所以如果一个部分是nan
的,我们可以认为复数被nan
(只要没有无限部分)。
该标准继续说,学校乘法在大多数情况下应该成立,但也
如果一个操作数是无穷大,另一个操作数是非零有限数或无穷大,则算子的结果是无穷大;
这意味着,例如,(1.0+0j)*(inf+inf*j)
应该是无限的(inf+inf*j
可能是最有意义的),但不是nan+nan*j
,因为通常的公式就是这种情况。
在我下面的SO问题中有更多关于这个主题的内容。
鉴于编译器具有一定的自由度产生结果,我们可以看到,通过__multdc3
使用的实现与简化的学校公式之间的区别仅在考虑有符号零的情况下,即(-0,-0)vs.(0,-0)
等等(请参阅下面进一步测试的程序列表或在此处实时查看)。
这意味着,gcc 的行为是可以的,因为它使用了标准的未定义行为。有人可能会争辩说,这是错过了对叮当声的优化。
注意:还有一个"错误报告":https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84891
#include <complex>
#include <iostream>
#include <cmath>
#include <cfloat>
#include <vector>
int get_type(std::complex<double> c){
if(std::isinf(c.real()) || std::isinf(c.imag()))
return 2;
if(std::isnan(c.real()) || std::isnan(c.imag()))
return 1;
return 0;
}
void do_mult(double b, double c, double d){
std::complex<double> school(-b*d, b*c);
std::complex<double> f(0.0,b);
std::complex<double> s(c,d);
auto cstd=f*s;
int type1=get_type(school);
int type2=get_type(cstd);
#ifdef INFINITE_MATH
//not special, usual
if(type1!=type2 || (type1==0 && (cstd!=school))){
std::cout<<"(0.0,"<<b<<")*("<<c<<","<<d<<")="<<school<<"vs."<<cstd<<"n";
}
#endif
#ifdef SIGNED_ZERO_MATH
// signed zero
if(type1!=type2 || (type1==0 && (1.0/cstd.real()!=1.0/school.real() || 1.0/cstd.imag()!=1.0/school.imag() ))){
std::cout<<"(0.0,"<<b<<")*("<<c<<","<<d<<")="<<school<<"vs."<<cstd<<"n";
}
#endif
}
int main(){
std::vector<double> numbers{0.0, -0.0, 1.0, INFINITY, -INFINITY, NAN, DBL_MAX, -DBL_MAX};
for(double b: numbers)
for(double c: numbers)
for(double d: numbers)
do_mult(b,c,d);
}
要构建/运行使用:
g++ main.cpp -o main -std=c++11 -DINFINITE_MATH -DSIGNED_ZERO_MATH && ./main
- cmath 是否借用了 math.h 的实现
- exp(-1/0.) == 0 是否确定即使使用 -ffast-math 优化也能成立?
- Math.log是以一种避免log(1 x)的精度的方式实现的
- 使用 boost::math::ibeta 的错误
- 是否有任何指数函数在 math.h 标头中返回IEEE_FLOAT64?
- 将 math.h exp 调用替换为查找表
- 在DirectX Math中与XmmatrixDecompose()相反
- 使用 gcc 的 -fno-math-errno 可能有什么副作用?
- 为什么仅 -fno-signed-0 就可以实现优化,而似乎也需要 -ffinite-math-only (gcc)
- 为什么没有标准的C++数学库<math>而不是C包装器<cmath>?
- 检测`boost :: Math ::工具:: brent_find_minima()的不良输入
- 使用boost :: Math的Gauss-Kronrod正交正交进行复杂函数的集成
- 不能使用包含 math.h 和 g++ 7 (Raspberry PI) 的 C 库
- boost::math::sinc_pi是否不必要地复杂
- C++ 错误:不存在从 "math::Vec3<float>" 到 "float" 的合适转换函数
- <math.h>是C还是C++?
- Spoj :ENIGMATH - PLAY WITH MATH
- 未解析的外部符号"private: static int Math::result"
- 对"Math::addNumbers(int, int)"的未定义引用
- 将gsl_ran_hypergeometric_pdf(k,n1,n2,t)转换为BOOST :: MATH :: HY