定义浮点等价关系的标准方法

Standard-compliant way to define a floating-point equivalence relationship

本文关键字:标准 方法 等价关系 定义      更新时间:2023-10-16

我知道浮点运算和精度损失的常见问题,所以这不是关于为什么0.1 + 0.2 != 0.3之类的常见问题。

相反,我实际上想在C++中实现一个二进制谓词(以 100% 符合标准的方式),它实际上实现了真正的数学等价关系(即自反、传递和对称),这样如果两个双精度在所有方面都表示完全相同的值,则它们处于相同的等价类中,区分0.0-0.0等极端情况,但将所有NaN值视为属于同一等价类。特别是,默认==不是我想要的,因为在NaN的情况下是非自反的,并且不区分0.0和负-0.0,我希望它们在不同的等价类中,因为它们实际上是不同的值并导致不同的运行时行为)。

执行此操作的最短和最简单的方法是什么,它不以任何方式依赖于类型双关语或任何实现定义的行为?到目前为止,我已经得到了:

#include <cmath>
bool equiv(double x, double y)
{   
return (x == y && (x != 0.0 || std::signbit(x) == std::signbit(y))) ||
(std::isnan(x) && std::isnan(y));
}

我相信这处理了我之前知道和描述的极端情况,但是还有其他我错过的角落案例无法处理吗?上面的二元谓词是否保证根据C++标准定义等价关系,或者是否有任何行为未指定、实现定义等?

看起来不错。

您实际上可以摆脱实现IEEE 754的平台(Intel,Power和ARM的函数调用),因为可以在没有调用的情况下确定特殊的浮点值。

bool equiv(double x, double y) {
return (x == y && (x || (1 / x == 1 / y))) || (x != x && y != y);
}

以上使用了IEEE的事实:

  • 非零除以零将产生保留符号的无穷大特殊值。因此1 / -0.产生-infinity。具有相同符号的无穷大特殊值比较相等。
  • NaN 不能平等地比较。

不过,原始版本对大多数人来说读起来更好。从面试经验来看,并不是每个开发者都知道特殊浮点值是如何产生的和行为的。

如果 NaN 有一个表示,你可以做memcmp.


关于C++和C语言标准,新C标准书说:

经常听到术语IEEE浮点。这种用法的产生是因为IEEE发布了有关此主题的原始标准。二进制浮点运算的这种标准是许多主机处理器十多年来一直提供的。但是,C99并未强制要求其使用。

本标准中指定的二进制浮点表示形式由英特尔 x86 处理器家族、Sun SPARC、HP PA-RISC、IBM P OWER PC、HP–WAS DEC – ALPHA 和大多数现代处理器使用(某些 DSP 处理器出于成本/性能原因支持子集或进行小幅更改;而其他处理器则有更实质性的差异,例如, TMS320C3x 使用二的补码)。该标准还有一个公开的软件实现。

处理器(IBM 390 和 HP-was DEC – VAX)仍然支持其他表示形式,这些处理器具有在该标准所基于的文档发布之前就拥有现有的客户群。这些表示形式可能会继续支持一段时间,因为现有的代码依赖于 (IBM 390 和 HP–was DEC– Alpha 支持其公司各自的旧表示和 IEC 60559 要求)。

人们普遍认为,一旦指定了IEC 60559标准,其所有必需的功能都将由一致的实施提供。由于这种常见的错误信念(编写文档的人并不总是熟悉此标准的人),C程序对IEC 60559结构的依赖(可能因实现而异)可能不会被记录下来。

与C标准一样,IEC 60559标准并未完全指定每个构造的行为。它还为某些构造提供可选行为,例如引发下溢时,并具有实现可能会也可能不使用的可选构造,例如双重标准。C99 并不总是提供一种在这些可选区域中找出实现行为的方法。例如,没有描述处理下溢的各种选项的标准宏。

每个计算机科学家都应该知道的关于浮点运算的内容是:

语言和编译器

多义性

理想情况下,语言定义应该足够精确地定义语言的语义,以证明关于程序的陈述。虽然这通常适用于语言的整数部分,但在浮点方面,语言定义通常具有很大的灰色区域。也许这是因为许多语言设计者认为无法证明浮点数,因为它需要舍入误差。如果是这样,前面的章节已经证明了这种推理的谬误。本节讨论语言定义中的一些常见灰色区域,包括有关如何处理它们的建议。

。大多数语言定义中的另一个歧义涉及溢出、下溢和其他异常会发生什么。IEEE标准精确地规定了异常的行为,因此使用该标准作为模型的语言可以避免在这一点上出现任何歧义。

。另一个灰色地带涉及括号的解释。由于舍入误差,代数的结合定律不一定适用于浮点数......如上所述,无论语言标准是否规定必须遵守括号,(x+y)+z 都可以与 x+(y+z) 有完全不同的答案。

....舍入可能是一个问题。IEEE标准非常精确地定义了舍入,它取决于舍入模式的当前值。这有时与类型转换中的隐式舍入定义或语言中的显式舍入函数相冲突。

语言标准不可能指定浮点运算的结果,因为例如,可以使用std::fesetround在运行时更改舍入模式。

因此,C 和 C++ 语言别无选择,只能将浮点类型的操作直接映射到硬件指令,而不会像它们那样进行干扰。 因此,这些语言不会复制IEEE/IEC标准,也不会强制要求它。