C++ -Ofast在使用长双精度时生成错误代码 #包括 内部主(空) { int-val=500; printf(“%d\n”,(int)((长双精度)val/500); printf(“%d\n”,(int)((长双精度)500/500)); }
显然,它应该输出C++ -Ofast在使用长双精度时生成错误代码 #包括 内部主(空) { int-val=500; printf(“%d\n”,(int)((长双精度)val/500); printf(“%d\n”,(int)((长双精度)500/500)); },c++,c,compilation,g++,compiler-optimization,C++,C,Compilation,G++,Compiler Optimization,显然,它应该输出11。但如果您使用AST的-Ofast编译它,它将输出01,为什么 如果您将500更改为其他值(例如400)并使用-Ofast进行编译,它仍将输出11 带有-Ofast的编译器资源管理器: 这条线似乎是问题的根源 -Ofast 无视严格的标准遵从性-Ofast启用所有-O3优化。它还支持对所有标准兼容程序无效的优化。它打开-ffast math,-fallow存储数据竞争和Fortran专用[…] -ffast math 设置选项-fno math errno,-funsafe
11
。但如果您使用AST的-Ofast
编译它,它将输出01
,为什么
如果您将500
更改为其他值(例如400
)并使用-Ofast
进行编译,它仍将输出11
带有-Ofast
的编译器资源管理器:
这条线似乎是问题的根源
-Ofast
无视严格的标准遵从性-Ofast
启用所有-O3
优化。它还支持对所有标准兼容程序无效的优化。它打开-ffast math
,-fallow存储数据竞争
和Fortran专用[…]
-ffast math
设置选项-fno math errno
,-funsafe math optimizations
,-ffinite math only
,-fno rounding math
,-fno signing nans
,-fcx limited range
和-fexcess precision=fast
此选项导致定义预处理器宏\uuu FAST\u MATH\uu
除了AST的-ofat
之外,任何-O
选项都不会启用此选项,因为它可能会导致依赖于精确实现IEEE或ISO数学函数规则/规范的程序输出不正确。但是,对于不需要这些规范保证的程序,它可能会产生更快的代码
结论:不要使用
-ffast math
,除非你愿意得到像现在这样的惊喜。使用-Ofast
,-ffast math
启用,这可能会导致以不同且更快的方式计算某些操作。在您的情况下,(长双精度)val/500)
可以计算为(长双精度)val*(1.0L/500))
。当您比较以下功能的-O2
和-Ofast
时,可以在生成的程序集中看到这一点:
long double f(long double a)
{
return a / 500.0L;
}
使用-O2
生成的程序集涉及fdiv
指令,而使用-Ofast
生成的程序集涉及fmul
指令,请参阅
接下来,1/500,即0.002,不能用长双精度表示。因此,出现了一些舍入,而且在您的情况下,这种舍入似乎是向下的。这可以通过以下表达式进行检查:
500.0L * (1.0L / 500.0L) < 1.0L
500.0升*(1.0升/500.0升)<1.0升
其计算结果为true
:。因此,精确存储的乘数是0.002-一些非常小的增量
最后,乘法的结果是500*(0.002-增量)=1-一些小值。当这个值被转换成
int
时,它会被截断,因此int
中的结果是0。即使显示的程序段有“问题”,处理浮点数的方法也是错误的
您或多或少会询问程序浮点数是否具有“精确值”——在本例中为“1”。好的,更准确地说,如果值为“<1”或“>=1”的值是“大约”1,那么正好是两个答案的除法极限。但是,正如其他人已经写过的(或者很容易在维基百科中找到,…)浮点数的精度有限。因此,这种偏差可以而且将会发生
因此,得出一个结论:在进行浮点到整数的转换时,应该始终使用舍入,即“(int)round(floating_point_value)”
注:与其他人可能说的或建议相反——我看不出有任何问题——ffast数学计算。唯一的“问题”是在不同计算机上运行某个程序后(按位)比较其结果
我用-ffast数学(实际上是-Ofast)进行所有(科学)计算。但到目前为止,这从来都不是一个问题——因为我预计浮点数会有一些舍入错误(这是真的,不管是否使用-ffast math)——但就我所知,仅此而已。由于我通常使用64位浮点(双精度),这意味着计算精确到大约15到17位十进制数字——最后(少数)位会出现这些不准确的情况——仍然给我提供了很多“精确”的数字——比如说——超过13位,取决于我的计算有多复杂。将
val
更改为小于500将得到0。但我不确定您是否认为(int)(500./500)
显然是1。在第一种情况下,可能是快速优化导致编译器生成的代码乘以0.0019999999
,而不是除法。在第二种情况下,它可能根本不会在运行时计算。@cigien这不是重点。关键是两个printf
语句应该输出相同的值,因为它们本质上是(int)(500./500)
。它们不必输出相同的值。在第二种方法中,编译器可以生成put(“1”)代码>只是猜测,但我认为启用了-ffast math
的编译器可以将/500
计算为*(1.0/500)
。如果此值向下舍入,则乘以(长双精度)500
可能会略低于1,并且强制转换为int
始终会截断,最终将生成0
。