f = NAN 可能导致引发浮点异常是否正常?

Is it considered normal that f = NAN may cause raising floating-point exceptions?

C2x(以及之前的):

The macro NAN is defined if and only if the implementation supports quiet NaNs for the float type. It expands to a constant expression of type float representing a quiet NaN.

示例代码 (t0a.c)

#include <stdio.h>
#include <math.h>
#include <fenv.h>

#if _MSC_VER && ! __clang__ && ! __INTEL_COMPILER
#pragma fenv_access (on)
#else
#pragma STDC FENV_ACCESS ON
#endif

void print_fe_excepts_raised(void)
{
    printf("exceptions raised ");
    if (fetestexcept(FE_DIVBYZERO))  printf(" FE_DIVBYZERO");
    if (fetestexcept(FE_INEXACT))    printf(" FE_INEXACT");
    if (fetestexcept(FE_INVALID))    printf(" FE_INVALID");
    if (fetestexcept(FE_OVERFLOW))   printf(" FE_OVERFLOW");
    if (fetestexcept(FE_UNDERFLOW))     printf(" FE_UNDERFLOW");
    if (fetestexcept(FE_ALL_EXCEPT)==0) printf(" none");
    printf("\n");
}

int main(void)
{
    float f;

    feclearexcept(FE_ALL_EXCEPT);
    f = NAN;
    print_fe_excepts_raised();
    (void)f;
    return 0;
}

调用:

# msvc (version 19.29.30133 for x64)
$ cl t0a.c /std:c11 /Za /fp:strict && t0a.exe
exceptions raised  FE_INEXACT FE_INVALID FE_OVERFLOW

# clang on Windows (version 13.0.0)
$ clang t0a.c -std=c11 -pedantic -Wall -Wextra -ffp-model=strict && ./a.exe
exceptions raised  FE_INEXACT FE_INVALID FE_OVERFLOW

# gcc on Windows (version 11.2.0)
$ gcc t0a.c -std=c11 -pedantic -Wall -Wextra  && ./a.exe
exceptions raised  none

# gcc on Linux (version 11.2.0)
$ gcc t0a.c -std=c11 -pedantic -Wall -Wextra  && ./a.out
exceptions raised  none

# clang on Linux (version 13.0.0)
$ clang t0a.c -std=c11 -pedantic -Wall -Wextra -ffp-model=strict && ./a.out
exceptions raised  none

对于 Windows 上的 msvc 和 clang:这是因为:

C:\Program Files (x86)\Windows Kits\Include.0.18362.0\ucrt\corecrt_math.h:94:9
#define NAN ((float)(INFINITY * 0.0F))

C:\Program Files (x86)\Windows Kits\Include.0.18362.0\ucrt\corecrt_math.h:90:9
#define INFINITY ((float)(_HUGE_ENUF * _HUGE_ENUF))

C:\Program Files (x86)\Windows Kits\Include.0.18362.0\ucrt\corecrt_math.h:87:13
#define _HUGE_ENUF 1e+300 // _HUGE_ENUF*_HUGE_ENUF must overflow

这里我们看到 NAN“扩展为表示安静 NaN 的 float 类型的常量表达式”。这意味着 f = NAN 可能会导致浮点异常。但是,f = NAN 通常被视为“写入内存”。因此,人们可能会想:“写入内存如何导致引发浮点异常?”。

郑重声明,这...

The macro NAN is defined if and only if the implementation supports quiet NaNs for the float type. It expands to a constant expression of type float representing a quiet NaN.

... 是 C17 7.12/5,它可能在 C2x 中具有相同或相似的编号。


已更新

当在 Windows 上与 MSVC 或 Clang 一起使用时,您的测试程序导致引发 FE_INVALID FP 异常 这一事实表明

  • Microsoft 的 C 标准库和 运行时间环境
  • MSVC 和 Clang 编译器和
  • 您指定的选项

导致生成信号 NaN 并将其用作算术操作数。我同意这是一个意想不到的结果,可能表明这些组合未能完全符合这方面的 C 语言规范。

与生成的 NaN 是安静值还是信号值无关。那里的误解是 FE_INVALID 标志只会由于生成或操作信号 NaN 而被引发。事实并非如此。

一方面,IEEE-754 没有定义生成信令 NaN 的任何情况。所有定义的产生 NaN 的操作都会产生一个安静的 NaN,包括其中一个操作数是一个信号 NaN 的操作(因此 Windows 上的 MSVC 和 Clang 几乎肯定会产生一个安静的 NaN 作为 NAN 的值宏)。默认情况下,大多数将至少一个信号 NaN 作为操作数的操作会导致 FE_INVALID 标志被引发,但这不是引发该标志的通常原因。

相反,在默认的异常处理下,FE_INVALID 标志只是因为请求计算一个没有定义结果的操作而被引发,例如无穷大乘以 0。结果将是一个安静的 NaN。请注意,这不包括具有至少一个 NaN 操作数的操作,do 具有定义的结果:在许多情况下为安静的 NaN,用于比较的无序/假,以及少数其他结果案例。

关于上下文,重要的是要认识到,仅仅因为 NAN 扩展为常量表达式(在一致的 C 实现中)并不意味着该表达式的值是在编译时计算的。事实上,考虑到 MSVC 和 Clang 的严格 fp 模式的规范,我希望这些模式能够禁用大部分(如果不是全部的话)FP 表达式的编译时计算(或者至少传播 FP 状态标志,就好像计算是在 运行次)。

因此,提高 FE_INVALID 不一定是 f = NAN 中赋值的结果。如果(如在 Microsoft 的 C 标准库中)NAN 扩展为涉及算术运算的表达式,则应在计算该表达式时引发异常,尽管生成的 NaN 是安静的。至少在通过定义 __STDC_IEC_559__ 功能测试宏来声称完全符合 IEC 60559 的实现中。

因此,虽然我不会对此提出异议

people may wonder: "how writing to memory may cause raising floating-point exceptions?".

,没有令人信服的证据表明已经观察到这种因果关系。

然而,在被计算的表达式中由 NAN 的特定外观表示的值具有某种物理表现形式。这在 FPU 寄存器中是合理的,并且将信号 NaN 从 FPU 寄存器存储到内存确实可能导致在某些体系结构上引发 FP 异常。