我已经意识到浮点运算和精度损失的常见问题,所以这不是关于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 ++标准定义等价关系,还是任何未指定的行为,实现定义等等?
答案 0 :(得分:4)
看起来不错。
实际上你可以去掉那些实现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有一个代表,你可以memcmp
。
关于C ++和C语言标准,The New C Standard书说:
经常听到IEEE浮点这个术语。之所以出现这种情况,是因为该主题的原始标准由IEEE发布。二进制浮点运算的这个标准是许多主机处理器十多年来提供的。但是,它的使用并非C99强制要求。
此标准中指定的二进制浮点表示由Intel x86处理器系列,Sun SPARC,HP PA-RISC,IBM P OWER PC,HP-DEC-Alpha以及大多数现代处理器使用(出于成本/性能原因,一些DSP处理器支持子集或进行小的改动;而其他DSP处理器则有更大的差异,例如,TMS320C3x使用两个补码)。此标准还有一个公开可用的软件实现。
处理器(IBM 390和HP-DEC-VAX)仍然支持其他表示形式,其现有客户群早于发布本标准所基于的文档。由于现有的代码依赖,这些表示可能会在一段时间内继续得到支持 在它上面(IBM 390和HP-是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标准,也不会强制要求。