我知道使用 ==检查浮点变量的相等性不是一个好方法。但是我只想知道以下的陈述:
==
float x = ... float y = x; assert(y == x)
因为 y是从 x复制的,所以断言是真的吗?
y
x
在通常的情况下,它的计算结果为 true (或者断言语句什么也不做)
编辑 :
我所说的“通常情况”是指不包括其他用户指出的上述场景(比如 NaN 值和80x87浮点单位)。
鉴于8087芯片在今天的情况下已经过时,这个问题是相当孤立的,并且这个问题适用于所使用的浮点体系结构的当前状态,对于除 NaN 以外的所有情况都是正确的。
(关于8087-https://home.deec.uc.pt/~jlobo/tc/artofasm/ch14/ch143.htm的参考文献)
@ chtz 再现了一个好例子,@kmdreko 提到了 NaNs,这是值得称赞的——以前从来不知道它们!
如果 x是 NaN,那就不是真的,因为 NaN上的比较是 总是错的(是的,甚至是 NaN == NaN)。对于所有其他情况(正常值、次正常值、无穷大、零) ,这个断言都是正确的。
NaN
NaN == NaN
避免浮点数使用 ==的建议适用于 计算,因为在算术表达式中使用浮点数时,无法准确地表达许多结果。赋值不是计算,没有理由赋值会产生与原始值不同的值。
如果遵循标准,扩展精度计算应该是没有问题的:
除了赋值和强制转换(它删除了所有额外的范围和精度) ,对带有浮动操作数的操作的值和受常规算术转换约束的值以及浮动常量的值进行计算,其格式的范围和精度可能大于该类型的要求。
但是,正如注释所指出的,在某些情况下,某些编译器、构建选项和目标 可以使这种说法自相矛盾地成为错误。
是的,y肯定会承担 x的价值:
[expr.ass]/2 : 在简单赋值(=)中,修改左操作数引用的对象([ defns.access ]) ,将其值替换为右操作数的结果。
[expr.ass]/2
没有余地分配其他值。
(其他人已经指出,对于 NaN 值,等效比较 ==仍将评估为 false。)
false
浮点 ==的常见问题是,很容易使 没有具有您认为具有的相当大的值。在这里,我们知道这两个值,不管它们是什么,都是相同的。
除了 kmdreko 指出的 assert(NaN==NaN);情况之外,还可以使用 x87-數学,将80位浮点数临时存储到内存中,然后与仍然存储在寄存器中的值进行比较。
assert(NaN==NaN);
可能的最小示例,在使用 -O2 -m32编译 gcc9.2时会失败:
-O2 -m32
#include <cassert> int main(int argc, char**){ float x = 1.f/(argc+2); volatile float y = x; assert(x==y); }
Godbolt 演示: https://godbolt.org/z/X-Xt4R
如果您设法创建了足够的寄存器压力,以便从内存中存储和重新加载 y,那么可以省略 volatile(但是要足够混淆编译器,不要全部省略比较)。
volatile
参见海湾合作委员会常见问题解答:
是的,在所有情况下(不考虑 NaNs 和 x87问题) ,这将是真实的。
如果对它们进行 memcmp测试,就能够在比较 NaNs 和 sNaNs 的同时测试它们是否相等。这还需要编译器获取变量的地址,这将强制该值转换为32位的 float,而不是80位的 float。这将消除 x87问题。这里的第二个断言旨在表明 ==不会将 NaNs 作为真值进行比较:
memcmp
float
#include <cmath> #include <cassert> #include <cstring> int main(void) { float x = std::nan(""); float y = x; assert(!std::memcmp(&y, &x, sizeof(float))); assert(y == x); return 0; }
注意,如果 NaN 具有不同的内部表示(即不同的尾数) ,则 memcmp不会比较 true。