【问题标题】:Is it considered normal that f = NAN may cause raising floating-point exceptions?f = NAN 可能导致引发浮点异常是否被认为是正常的?
【发布时间】:2021-12-24 00:21:52
【问题描述】:

C2x(以及以前的):

当且仅当实现支持 float 类型的安静 NaN 时,才会定义宏 NAN。它扩展为一个 float 类型的常量表达式,表示一个安静的 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\10\Include\10.0.18362.0\ucrt\corecrt_math.h:94:9
#define NAN ((float)(INFINITY * 0.0F))

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

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

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

【问题讨论】:

  • 首先,宏扩展为常量表达式这一事实不需要在编译时计算其值。如果扩展包含运算符并且上下文允许,则所涉及的操作可以在运行时进行评估。在这种情况下,可能是浮点 NaN 的计算导致引发 FP 异常,而不是结果分配的影响。
  • 所以微软的头文件定义了一个 NAN 但它并不安静。你发现他们偏离标准的地方有点。让我们忽略好奇的人,专注于您的问题。嗯,你的问题到底是什么?
  • 实际上,表达式产生的值很可能是一个安静的 NaN。 The whole thing seems fairly complicated.

标签: c floating-point c11 floating-point-exceptions c2x


【解决方案1】:

为了记录,这个...

当且仅当实现支持 float 类型的安静 NaN 时,才会定义宏 NAN。它扩展为一个 float 类型的常量表达式,表示一个安静的 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 操作数的操作,这些操作确实具有已定义的结果:在许多情况下是安静的 NaN,在比较时是无序/假,在少数情况下会产生其他结果。

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

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

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

人们可能想知道:“写入内存如何会导致引发浮点异常?”。

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

尽管如此,NAN 在被评估的表达式中的特定外观所代表的值具有某种物理表现。将其放在 FPU 寄存器中是合理的,并且将信号 NaN 从 FPU 寄存器存储到内存确实可能会导致在某些架构上引发 FP 异常。

【讨论】:

  • 回复:我希望 ...:根据测试,它们确实禁用了大多数(如果不是全部)FP 表达式的编译时计算。但是,这是not mentioned in the documentation
  • 它可能没有在文档中明确提及,@pmor,但它是实现 记录的行为的可预测要求。在任何情况下,仅仅因为某事可以完成就认为它会完成是不安全或不合理的。
  • 仅供参考:根据-ffp-model=strict 下的测试,clang 会在编译时继续计算 FP 表达式。
  • 如果确实如此,@pmor,那么 Clang 一定会遇到很多麻烦,还要在编译时捕获生成的 FP 状态并在运行时重新应用它。当然,这是一种有效的方法,这也与计算 NAN 表达式引起的 FP 异常在运行时不可见的前提相矛盾。
  • Re: Clang 一定会遇到很多麻烦:在编译时而不是在运行时进行 FP 计算也容易出错,因为 logic implementing FP operations in compiler's front end(通常是任意的精度)通常没有经过形式验证(因此可能会出现错误),而在 Intel CPU is formally verified 中实现 FP 操作的逻辑(因此没有错误)。
猜你喜欢
  • 2021-06-26
  • 2016-06-30
  • 2013-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多