这个GCC优化不正确吗?

我有一个函数接受一个值,将其转换为双精度,取出日志并将其值恢复为原始types。 对于这个问题的目的,原始和输出types是浮动的。 这是原始的C ++代码:

return static_cast< TOutput >( std::log( static_cast< double >( A ) ) ) 

当我在debugging模式下编译时,一切都按预期进行,GCC调用底层的logfunction:

  51:/myfile.h **** return static_cast< TOutput >( std::log( static_cast< double >( A ) ) ); 219133 .loc 112 51 0 219134 0010 488B45F0 movq -16(%rbp), %rax # A, tmp64 219135 0014 F30F1000 movss (%rax), %xmm0 # *A_1(D), D.237346 219136 0018 0F14C0 unpcklps %xmm0, %xmm0 # D.237346, D.237346 219137 001b 0F5AC0 cvtps2pd %xmm0, %xmm0 # D.237346, D.237347 219138 001e E8000000 call log # 219138 00 219139 0023 660F14C0 unpcklpd %xmm0, %xmm0 # D.237347 219140 0027 660F5AC0 cvtpd2ps %xmm0, %xmm0 # D.237347, D.237346 219141 002b F30F1145 movss %xmm0, -20(%rbp) # D.237346, %sfp 219141 EC 219142 0030 8B45EC movl -20(%rbp), %eax # %sfp, <retval> 

但是,当我打开优化(-O2 -ggdb3 -DNDEBUG)时,它调用logf (???)函数:

  51:/myfile.h **** return static_cast< TOutput >( std::log( static_cast< double >( A ) ) ); 145171 .loc 64 51 0 145172 01a0 F30F1004 movss (%rdx,%rax,4), %xmm0 # MEM[(const float &)_84], MEM[(const float &)_84] 145172 82 145173 01a5 E8000000 call logf # 

它给出了不同的输出。 这是正常的吗? 我做错了什么? 在我看来,海湾合作委员会正在对我的代码进行非常自由的解释,在缺乏-ffast-math选项的情况下我不会期望这-ffast-math

将双精度log的应用程序的转换转换为float到单精度log的应用程序是一种边界优化,但可以认为是可以接受的。

假设logf正确舍入,并且双精度log也被正确舍入或者至少被忠实地舍入,那么这两个计算将很少不同。 它们可以因为双舍入 (其中“double”意思是“两次”而不涉及类型)而不同(对于一些稀有输入)。 双舍入在统计学上意义不大,因为中间类型的有效数与最终类型的有效数相比有额外的数字(从数学的观点来看,这个统计参数有点垃圾,但是对于函数没有被设计成反例)。 由于教学的原因,人们(或维基百科)用一到两个额外的精度来解释它,但是当你有二十二三十二十二十二十二个额外的二进制数字的时候,可以预期在2 月29日很少出现一次。

我对这个优化感到惊讶,如果我自己编写了代码,用log来详细搜索double-rounding的问题,我会感到不安的,但考虑到C ++标准并没有从std::log任何级别的准确性,有可能将其视为“不是一个错误”。


如果不是log ,我们一直在谈论其中的一个基本操作(比如* ),那么对于声称提供IEEE 754语义的编译器,当它引入可见的改变时,转换将是不正确的。 对于基本操作,准确度由IEEE 754间接规定,并且规格没有变化的余地。

对于基本操作来说,当floatOP(flx,fly)替换(float)doubleOP((double)flx, (double)fly)时,不会出现可见的变化floatOP(flx,fly)本文在第6章中进行了说明)当类型是doublelong double时可以是可见的差异。 这个确切的bug 最近由Stephen Canon在Clang 修复 。

是的,这个优化是不正确的。 loglogf只需要忠实地四舍五入,所以可以有

 logf(4) = 0x1.62e42p+0 log(4) = 0x1.62e42fefa39efp+0 

更改上变频, log和下变频为一个logf调用可能会导致不正确的结果。

Pascal Cuoq的回答正确地指出,如果logf被正确舍入并且log不是垃圾,那么结果可能不会有差别。 但是,在我的平台上的logf 正确舍入:

 logf(0x1.306p-138) = -0x1.7decc8p+6 (float)log(0x1.306p-138) = -0x1.7decc6p+6 mpfr_log(0x1.306p-138) = -0x1.7decc6ff8a7a4a4450e9p+6 

谢天谢地,我无法用我的gcc重现这个“优化”。

我已经尝试了一个等效的C程序:

 #include <math.h> #include <stdlib.h> #include <stdio.h> static double dostuff(float in) { return log(in); } int main(int argc, char **argv) { if (argc < 2) exit(EXIT_FAILURE); float in = strtof(argv[1], NULL); float out = dostuff(in); printf("%f\n", out); return 0; } 

而且发现即使使用-Ofastgcc也不使用logf-Ofast唯一启用的是使用__log_finite() 。 但是,将dostuff()的返回类型更改为float启用此优化。

你不应该关心不同的输出。 如果你把一个float值赋给double值,然后返回到float值; 你可以得到不同的价值; 因此,通过将float转换为double值,执行日志操作,然后将结果强制转换回float,得到的值不会比通过直接调用logf函数获得的值更好。

如果您对操作的精确性有任何疑虑,那么您根本不应该使用浮点类型,所有的数学值都应该是双倍的。

通过使用浮点类型,可以告知编译器,除了浮点类型提供的算术运算以外,没有特别注意数学运算的最终精度。 换句话说,当你用实数(除了整数之外的数字)进行一些数学运算时,精度只能保持相同或减小; 它永远不会增加; 因此,如果您以浮点类型开始,则无法将该类型的精度提高。