Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/124.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 在同时使用+;接受0.0或-0.0_C++_Optimization_Floating Point_Comparison_Zero - Fatal编程技术网

C++ 在同时使用+;接受0.0或-0.0

C++ 在同时使用+;接受0.0或-0.0,c++,optimization,floating-point,comparison,zero,C++,Optimization,Floating Point,Comparison,Zero,到目前为止,我有以下几点: bool IsZero(const double x) { return fabs(x) == +0.0; } 这是比较精确0的最快正确方法,而+0.0和-0.0都被接受吗 如果CPU特定,请考虑X8664。如果编译器特定,让我们考虑MSVC+++ 2017工具集V141../P>< P>简单明了的单词,如果你想完全接受+0和-0,你只需使用: x==0.0 或 从cmath库中,您可以使用: int fpclassify(双参数),它将以简单明了的文字返回-0

到目前为止,我有以下几点:

bool IsZero(const double x) {
  return fabs(x) == +0.0;
}
这是比较精确0的最快正确方法,而
+0.0
-0.0
都被接受吗


如果CPU特定,请考虑X8664。如果编译器特定,让我们考虑MSVC+++ 2017工具集V141../P>< P>简单明了的单词,如果你想完全接受+0和-0,你只需使用:

x==0.0

从cmath库中,您可以使用:


int fpclassify(双参数),它将以简单明了的文字返回-0.0或+0.0的“零”,如果您想准确地接受+0.0和-0.0,您只需使用:

x==0.0

从cmath库中,您可以使用:


int fpclassify(double arg),对于-0.0或+0.0将返回“零”

如果打开代码的汇编器,您可以找到不同版本的代码使用的是哪种汇编器指令。有了汇编程序,你可以估计哪一个更好

在GCC编译器中,您可以通过以下方式保留中间文件(包括汇编程序版本):

gcc-保存temps main.cpp


如果您打开代码的汇编器,您可以找到不同版本的代码所使用的汇编器指令。有了汇编程序,你可以估计哪一个更好

在GCC编译器中,您可以通过以下方式保留中间文件(包括汇编程序版本):

gcc-保存temps main.cpp


既然你说你想要最快的代码,我将在这个答案中做一些重要的简化假设。根据问题,这些都是合法的。特别是,我假设x86和IEEE-754表示浮点值。在适用的情况下,我还将提到MSVC特有的怪癖,尽管一般性讨论将适用于任何针对这种体系结构的编译器

测试浮点值是否等于零的方法是测试其所有位。如果所有位均为0,则该值为零。实际上,这个值是+0.0。符号位可以是0或1,因为表示法允许正0.0和负0.0,正如您在问题中提到的那样。但这种差异实际上并不存在(实际上不存在+0.0和+0.0之类的东西)−0.0),所以您真正需要的是测试除符号位之外的所有位

这可以通过一些小动作快速有效地完成。在像x86这样的小端体系结构上,符号位是前导位,因此只需将其移出,然后测试其余的位

阿格纳·福格(Agner Fog)在他的小说中描述了这个把戏。具体来说,示例17.4b(当前版本第156页)

对于32位宽的单精度浮点值(即,
float
):

mov   eax, DWORD PTR [floatingPointValue]
add   eax, eax        ; shift out the sign bit to ignore -0.0
sete  al              ; set AL if the remaining bits were 0
将其转换为C代码,您可以执行以下操作:

const uint32_t bits = *(reinterpret_cast<uint32_t*>(&value));
return ((bits + bits) == 0);
丑陋的,潜在的缓慢。有一些无分支的方法可以做到这一点,但MSVC不会使用它们

上述“优化”实现的一个明显缺点是,它需要从内存加载浮点值才能访问其位。没有x87指令可以直接访问位,也没有办法不经过内存直接从x87寄存器进入GP寄存器。由于内存访问速度慢,这会导致性能下降,但在我的测试中,它仍然比预测失误的分支快

如果您在32位x86上使用任何标准调用约定(
\uuu cdecl
\uu stdcall
,等等),那么所有浮点值都会在x87寄存器中传递和返回,因此从x87寄存器移到GP寄存器与从x87寄存器移到SSE寄存器没有区别

如果您以x86-64为目标,或者在x86-32上使用
\uuu vectorcall
,情况会有所不同。然后,实际上在SSE寄存器中存储并传递浮点值,因此可以利用无分支SSE指令。至少在理论上是这样。MSVC不会,除非你握住它的手。它通常会执行与上面所示相同的分支比较,只是没有额外的内存负载:

  ;; MSVC output for a __vectorcall function, targeting x86-32 with /arch:SSE2
  ;; and/or for x86-64 (which always uses a vector calling convention and SSE2)
  ;; The floating point value being compared is passed directly in XMM0
  ucomiss   xmm0, DWORD PTR [constantZero]
  lahf
  test      ah, 44h
  jp       IsNonZero
  mov      al, 1
  ret
IsNonZero:
  xor      al, al
  ret
我已经演示了一个非常简单的
booliszero(float val)
函数的编译器输出,但是在我的观察中,MSVC总是为这种类型的比较发出
UCOMISS
+
JP
序列,不管比较是如何合并到输入代码中的。同样,如果输入的零度是可预测的,那么这很好,但是如果分支预测失败,那么情况相对糟糕

如果希望确保获得无分支代码,避免分支预测失误暂停的可能性,那么需要使用intrinsic进行比较。这些内部函数将迫使MSVC发出更接近您预期的代码:

return (_mm_ucomieq_ss(_mm_set_ss(floatingPointValue), _mm_setzero_ps()) != 0);
不幸的是,输出仍然不完美。您在使用内部函数时会遇到一些普遍的优化缺陷,即在各种SSE寄存器之间对输入值进行冗余洗牌,但这(A)是不可避免的,(B)不是可测量的性能问题

这里我要指出的是,其他编译器,如Clang和GCC,不需要手动。您只需执行
value==0.0
。它们发出的代码的确切顺序因您的优化设置而异,但您将看到
COMISS
+
SETE
UCOMISS
+
SETNP
+
CMOVNE
CMPEQS
+
MOVD
+
NEG
(后者由ICC独家使用)。您试图用内部函数握住他们的手几乎肯定会导致效率较低的输出,因此可能需要
#ifdef
”将其限制在MSVC中

这是单精度值,宽度为32位。两倍长的双精度值呢?您可能认为这些将有63位需要测试(因为符号位仍然是ign)
  ;; MSVC output for a __vectorcall function, targeting x86-32 with /arch:SSE2
  ;; and/or for x86-64 (which always uses a vector calling convention and SSE2)
  ;; The floating point value being compared is passed directly in XMM0
  ucomiss   xmm0, DWORD PTR [constantZero]
  lahf
  test      ah, 44h
  jp       IsNonZero
  mov      al, 1
  ret
IsNonZero:
  xor      al, al
  ret
return (_mm_ucomieq_ss(_mm_set_ss(floatingPointValue), _mm_setzero_ps()) != 0);
mov    eax, DWORD PTR [floatingPointValue+4]  ; load upper bits only
add    eax, eax        ; shift out sign bit to ignore -0.0
sete   al              ; set AL if the remaining bits were 0
const uint64_t bits      = *(reinterpret_cast<uint64_t*>(&value);
const uint32_t upperBits = (bits & 0xFFFFFFFF00000000) >> 32;
return ((upperBits + upperBits) == 0);
return (_mm_ucomieq_sd(_mm_set_sd(floatingPointValue), _mm_setzero_pd()) != 0);