这个GCC优化不正确吗?

sta*_*tti 24 c++ linux floating-point assembly gcc

我有一个functor,它接受一个值,将其转换为double,获取日志并将值转换回原始类型.出于此问题的目的,原始和输出类型是float.这是原始的C++代码:

return static_cast< TOutput >( std::log( static_cast< double >( A ) ) )
Run Code Online (Sandbox Code Playgroud)

当我在调试模式下编译时,一切都按预期进行,GCC调用底层log函数:

  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>
Run Code Online (Sandbox Code Playgroud)

但是,当我打开优化(-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  #
Run Code Online (Sandbox Code Playgroud)

它给出了不同的输出.这是正常的吗?我做错了吗?在我看来,海湾合作委员会对我的代码采取了非常自由的解释,在没有-ffast-math选项的情况下我不会期望.

Pas*_*uoq 33

这是一个边界优化,将float双精度应用程序的转换转换为log浮点数到单精度应用程序log,但可以认为它是可以接受的.

假设logf正确舍入并且双精度log也正确舍入或至少忠实地舍入,两个计算将很少有所不同.它们可以不同(对于一些罕见的输入),因为双舍入(其中"double"表示"两次"并且不指代类型).与最终类型的有效数相比,中间类型的有效数字中存在额外数字的双舍入在统计上是不太重要的(并且这个统计参数从数学的角度来看稍微有些垃圾,但它"在实践中"用于没有被设计为反例).出于教学原因,人们(或维基百科)用一个或两个额外的精度数字解释它,但是当你有53 - 24 = 29个额外的二进制数字时,它可能会在2 29中很少发生一次.

我对优化感到惊讶,如果我自己编写代码来详尽搜索双舍入问题,我会感到不安log,但考虑到C++标准并没有强制要求任何精度std::log,有可能认为它"不是一个bug".


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

碰巧的是,对于基本操作,floatOP(flx,fly)替换时不会有明显的变化(float)doubleOP((double)flx, (double)fly)(本文在第6章中对此进行论证),但是当类型为double和时,可能存在明显的差异long double.这个确切的错误最近由Stephen Canon在Clang中修复.

  • 我会把这一点牢牢地放在"标准可能允许的优化,但对用户的生活质量有害"的类别中,特别是当应用于明确旨在评估更广泛类型的源表达式时.它会伤害可预测性,并导致更多用户认为浮点是黑魔法.这大致类似于重新关联产品,我绝对不会在快速数学之外容忍. (7认同)
  • @StephenCanon我考虑将Clang补丁归功于Steven Canon. (4认同)