在 C++ 中检查具有内在函数的 nan

Posted

技术标签:

【中文标题】在 C++ 中检查具有内在函数的 nan【英文标题】:checking for nans with intrinsics in c++ 【发布时间】:2016-02-18 17:25:30 【问题描述】:

我是使用内在函数的新手,但我想编写一个函数,该函数采用 4 个双精度向量计算 a > 1e-5 ? std::sqrt(a) : 0.0 我的第一直觉是这样写

#include <immintrin.h>
__m256d f(__m256d a)

  __m256d is_valid = a > _mm256_set1_pd(1e-5);
  __m256d sqrt_val = _mm256_sqrt_pd(a);
  return is_valid * sqrt_val;

根据 gcc.godbolt.com 编译成如下

f(double __vector(4)):
    vsqrtpd  ymm1, ymm0
    vcmpgtpd ymm0, ymm0, YMMWORD PTR .LC0[rip]
    vmulpd   ymm0, ymm1, ymm0
    ret
.LC0:
    .long   2296604913
    .long   1055193269
    .long   2296604913
    .long   1055193269
    .long   2296604913
    .long   1055193269
    .long   2296604913
    .long   1055193269

但我担心如果sqrt_val 包含nan 会发生什么。我不认为0.0 * nan 会起作用。这里有哪些最佳做法?

编辑

在阅读了@ChrisCooper(和@njuffa)的评论后,我被链接到另一个堆栈溢出答案,所以我将测试自我平等,然后用我的结果测试and

#include <immintrin.h>
__m256d f(__m256d a)

  __m256d is_valid = a > _mm256_set1_pd(1e-5);
  __m256d sqrt_val = _mm256_sqrt_pd(a);
  __m256d result = is_valid * sqrt_val;
  __m256d cmpeq = result == result;
  return  _mm256_and_pd(cmpeq, result);
 

编译成如下

f(double __vector(4)):
    vsqrtpd  ymm1, ymm0
    vcmpgtpd ymm0, ymm0, YMMWORD PTR .LC0[rip]
    vmulpd   ymm0, ymm1, ymm0
    vcmpeqpd ymm1, ymm0, ymm0
    vandpd   ymm0, ymm1, ymm0
    ret
.LC0:
    .long   2296604913
    .long   1055193269
    .long   2296604913
    .long   1055193269
    .long   2296604913
    .long   1055193269
    .long   2296604913
    .long   1055193269

【问题讨论】:

您可以使用std::isnan 来检测NaN 值。 @JamesAdkison 会在他的内在类型上起作用吗?我不太确定(这很可能就是问题的重点) 这个答案看起来可能会有所帮助:***.com/a/15912796/1807078 根据vcmpgtpd 的文档,它返回一个“全1(比较真)或全0(比较假)的四字掩码”。所以在我看来,您需要做的就是使用vandpd 将此掩码应用于vsqrtpd 的结果。如果 vsqrtpd 的参数是 NaN,则掩码将为零,因为在这种情况下比较返回 false。 您错过了@njuffa 评论的重点。您根本不需要任何(慢速)乘法。您也不需要对 NaN 执行任何检查。 【参考方案1】:

我之前没有使用 AVX 内部函数进行编程,因此从文档中收集信息以快速组合以下代码。对于我提供的一个测试用例,它似乎可以正常工作。

相关的观察是比较指令返回全 1(如果结果为 TRUE)或全 0(如果结果为 FALSE)的掩码。然后,此掩码可用于通过将掩码与来自vsqrtpd 的结果进行与运算,有条件地将平方根的结果设置为零。 0.0 在 IEEE-754 双精度中的二进制表示全为 0。

之前没有使用过这些内在函数,我发现比较谓词很难使用。据我了解,这里我们希望使用有序比较来获得关于 NaN 的所需行为(即,与 NaN 的比较应该导致 FALSE),因此使用“O”变体。我们也不希望 NaN 输入触发异常(也就是说,我们希望在这种情况下比较安静),所以使用“Q”变体。这意味着我们要使用 _CMP_GT_OQ。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <immintrin.h>

__m256d f (__m256d a)

   double em5 = 1e-5;
   __m256d v_em5 = _mm256_broadcast_sd (&em5);
   __m256d v_sqrt = _mm256_sqrt_pd (a);
   __m256d v_mask = _mm256_cmp_pd (a, v_em5, _CMP_GT_OQ);
   __m256d v_res = _mm256_and_pd (v_sqrt, v_mask);
   return v_res;


int main (void)

    __m256d arg, res;
    double args[4] = 2e-5, sqrt(-1.0), 1e-6, -1.0;
    double ress [4] = 0;

    memcpy (&arg, args, sizeof(arg));
    res = f (arg);
    memcpy (ress, &res, sizeof(res));

    printf ("args = % 23.16e  % 23.16e  % 23.16e  % 23.16e\n", 
            args[0], args[1], args[2], args[3]);
    printf ("ress = % 23.16e  % 23.16e  % 23.16e  % 23.16e\n", 
            ress[0], ress[1], ress[2], ress[3]);
    return EXIT_SUCCESS;

我用 Intel C 编译器编译了上面的程序,输出如下:

args =  2.0000000000000002e-005  -1.#IND000000000000e+000   9.9999999999999995e-007  -1.0000000000000000e+000
ress =  4.4721359549995798e-003   0.0000000000000000e+000   0.0000000000000000e+000   0.0000000000000000e+000

这里,1.#IND000000000000e+000 是一个特定的 QNaN,称为 INDEFINITE。

【讨论】:

谢谢。我使用 gcc 5.3.0 测试了一个不同的版本 (goo.gl/lsjzY0),它支持 > 运算符而不是 _mm256_cmp_pd,我注意到的唯一区别是 > 运算符生成 vcmpltpd 指令而不是 vcmpgt_oqpd 指令。

以上是关于在 C++ 中检查具有内在函数的 nan的主要内容,如果未能解决你的问题,请参考以下文章

SSE 内在函数检查零标志

双重检查锁定的正确编译器内在函数?

检查 NaN 数据值 (C/C++/Python 实现)

如何检查单元测试中的值是不是为nan?

使用 console.log 检查时获取 Nan 输出

C++中宏替换与内联函数的区别