如何在 C 中安全地进行浮点运算

Duc*_*rZx 1 c floating-point precision ieee-754

最近的 (C23) 添加stdckdint.h提供了一种可移植的方法来检查整数算术中的溢出问题。我过去曾在用户提供相关整数的情况下使用过类似的编译器扩展,并且它们非常有帮助。现在我面临着一种情况,提供的值是浮点数,我需要对它们进行算术运算。

我的问题是是否有类似的解决方案(不一定在标准库中),以确保浮点变量(浮点数、双精度数、长双精度数)之间的操作不会遇到任何问题。如果不是,算术运算期间可能出现哪些问题(上溢、下溢等)以及如何检查这些问题?

Eri*_*hil 5

\n

如果不是,算术运算时可能出现的问题有哪些(上溢、下溢等)\xe2\x80\xa6

\n
\n

省略了 C 语言与 IEEE 754 浮点标准的一致性(标准和实现)的讨论,IEEE 754 在第 7 条中指定了五种例外:

\n
    \n
  • 当且仅当 \xe2\x80\x9c 没有有用的可定义结果\xe2\x80\x9d 时,发出无效操作信号:对信号 NaN 的一般操作、零与无穷大的乘法、相反符号无穷大的加法、相同符号的减法无穷大、零除零或无穷大除无穷大、小于零的数字的平方根、量化运算的某些用途(用于调整十进制格式的指数)、转换为无法表示结果的整数、NaN 的比较需要非 NaN 的运算符,以及 NaN、+\xe2\x88\x9e 或零的整数对数(\xe2\x80\x9c 获取浮点指数\xe2\x80\x9d)。
  • \n
  • 除以零是针对普通除以零和 \xe2\x88\x92\xe2\x88\x9e 的整数对数发出的信号。
  • \n
  • 当计算结果为无穷大时,会发出溢出信号,因为指数受浮点格式限制(而不是因为 \xe2\x80\x9c 自然地\xe2\x80\x9d 是无穷大)。
  • \n
  • 当计算结果为零时,会发出下溢信号,因为指数受浮点格式限制(不是因为 \xe2\x80\x9c 自然地\xe2\x80\x9d 为零),除非实现也可能在结果时发出下溢信号舍入之前的大小低于最小可表示的非零数。
  • \n
  • 当计算的浮点结果与实数算术结果不同时,会发出不精确信号。(不精确是浮点运算的常见结果,因此它被认为是正常的,而不是通用语言意义上的异常,除非在特殊用途的代码中。)
  • \n
\n

在此上下文中,\xe2\x80\x9csignaled\xe2\x80\x9d 表示以某种方式指示异常,例如在状态寄存器中设置一个位。它并不意味着程序执行被中断或改变,就像 Unix 信号导致信号处理程序被执行一样。以上是概要,省略了一些细节。

\n
\n

\xe2\x80\xa6 如果不是,算术运算期间可能出现的所有问题(上溢、下溢等)是什么以及如何检查它们?

\n
\n

当前的 C 标准(2018 年)和即将推出的标准(如果忠实于可用草案)将在第 7.6 条中提供用于测试浮点异常的工具,并讨论标头<fenv.h>及其相关工具。7.6.4 中规定了异常测试。

\n

feclearexcept(FE_ALL_EXCEPT)尝试清除所有支持的异常标志并在成功时返回零。

\n

fetestexcept(mask)mask返回设置的异常标志的按位与。掩码可以是FE_DIVBYZEROFE_INEXACTFE_INVALIDFE_OVERFLOWFE_UNDERFLOW以及可能由特定 C 实现支持的其他标志名称的按位或。

\n

以这种方式测试异常可能会很慢,具体取决于 C 实现及其底层硬件,因为它可能需要以中断处理器指令执行的方式访问状态寄存器,或者因为它要求编译器在以下情况下使用标量浮点指令:如果未访问标志,它可能已优化为使用 SIMD 指令。某些 C 实现可能提供测试标志的替代方法。特别是,为了性能,最好不要显式测试标志,而是设置在发生异常时触发的异常处理程序。这对于检测您期望罕见的异常非常有用,例如旨在避免它们的代码溢出。然而,对于常见的异常,例如不精确(通常很常见)或下溢(在某些代码中并不罕见,例如回声效果减弱),频繁执行异常处理程序将损害性能。

\n