【问题标题】:Standard-compliant way to define a floating-point equivalence relationship定义浮点等价关系的符合标准的方法
【发布时间】:2015-05-16 06:32:27
【问题描述】:

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

相反,我实际上想在 C++ 中实现一个二进制谓词(以 100% 符合标准的方式),它实际上实现了一个真正的数学 equivalence relationship(即自反、传递和对称) ),这样如果两个双精度值在所有方面都表示完全相同的值,它们就属于同一个等价类,区分像 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++ 标准定义等价关系,或者任何行为未指定、实现定义等?

【问题讨论】:

  • NaN 可以有不同的“NaN 有效载荷”,尽管您可能并不关心,但它们按位不同。
  • @Trantorian 你有没有想过用定点小数来代替?
  • -0 和 0 在数学上是等价的。
  • x != 0.0 可能是多余的,但在我看来是正确的

标签: c++ floating-point


【解决方案1】:

看起来不错。

您实际上可以摆脱对实现 IEEE 754(英特尔、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 语言标准,The New C Standard book 说:

经常听到术语 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 并不总是提供在这些可选区域中找出实现行为的方法。例如,没有标准的宏来描述处理下溢的各种选项。

What Every Computer Scientist Should Know About Floating-Point Arithmetic 说:

语言和编译器

模棱两可

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

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

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

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

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

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

【讨论】:

  • 但 C++ 标准并没有强制要求 IEEE 754,所以我认为它不符合 OP 的要求。
  • @5gon12eder C++ 标准未指定浮点规范,因为它无法完整地复制 IEEE 并对其进行跟踪。
  • @Trantorian 我认为这个答案不适用于-0.0, -0.0
  • 我会坚持使用 std:: 函数。对单元测试进行微优化没有多大价值。
  • @MaximEgorushkin 我想你的意思是(x == y &amp;&amp; (x || (1 / x == 1 / y))) || (x != x &amp;&amp; y != y)
猜你喜欢
  • 2015-02-28
  • 2011-09-27
  • 2015-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-23
相关资源
最近更新 更多