如果我将一个浮点数复制到另一个变量,它们会相等吗?

Wei*_* Li 167 c++ floating-point

我知道使用==检查浮点变量的相等性不是一个好方法。但我只想通过以下陈述来了解这一点:

float x = ...

float y = x;

assert(y == x)
Run Code Online (Sandbox Code Playgroud)

既然y是从 复制的x,那么这个断言是真的吗?

cht*_*htz 129

除了assert(NaN==NaN);kmdreko 指出的情况之外,您还可能遇到 x87-math 的情况,即 80 位浮点数临时存储到内存中,然后与仍存储在寄存器中的值进行比较。

可能的最小示例,在编译时使用 gcc9.2 失败-O2 -m32

#include <cassert>

int main(int argc, char**){
    float x = 1.f/(argc+2);
    volatile float y = x;
    assert(x==y);
}
Run Code Online (Sandbox Code Playgroud)

Godbolt 演示:https ://godbolt.org/z/X-Xt4R

volatile大概可以忽略,如果你管理,以产生足够的寄存器压力已经y存储并从内存重新加载(但迷惑编译器就够了,不要省略比较全在一起)。

请参阅 GCC 常见问题参考:

  • @Nat 这很奇怪;这是[一个错误](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323)。 (13认同)
  • @ThomasWeller 不,这是一个合理的奖励。虽然我希望答案指出这是不合规行为 (13认同)
  • 我可以扩展这个答案,指出汇编代码中到底发生了什么,这实际上违反了标准——尽管我不会称自己为语言律师,所以我不能保证不存在晦涩难懂的地方明确允许该行为的子句。我认为OP更感兴趣的是实际编译器的实际复杂性,而不是完全无错误、完全兼容的编译器(我猜这实际上不存在)。 (4认同)
  • 值得一提的是,“-ffloat-store”似乎是防止这种情况的方法。 (4认同)
  • 在将标准精度的“float”与额外精度进行比较时会考虑额外的位,这似乎很奇怪。 (2认同)
  • @chtz根据规范,仅需要进行舍入和转换,因此您的第一个示例很好。 (2认同)

kmd*_*eko 119

这不会是如果是真的xNaN,因为比较的NaN总是假的(是的,即使NaN == NaN)。对于所有其他情况(正常值、次正常值、无穷大、零),此断言将是正确的。

避免==使用浮点数的建议适用于计算,因为在算术表达式中使用浮点数时无法准确表达许多结果。赋值不是计算,并且没有理由赋值会产生与原始值不同的值。


如果遵循标准,扩展精度评估应该不是问题。从<cfloat>继承自 C [5.2.4.2.2.8](强调我的):

除了赋值和强制转换(删除所有额外的范围和精度),带有浮点操作数的操作的值和受通常算术转换影响的值和浮点常量的值被评估为一种格式,其范围和精度可能大于类型。

然而,正如评论所指出的,某些编译器、构建选项和目标的某些情况可能会使这自相矛盾。

  • @evg 当然!我的回答只是遵循标准。如果您告诉编译器不符合要求,那么所有的赌注都会失败,尤其是在启用快速数学时。 (23认同)
  • @Voo 请参阅我的答案中的引用。右侧的值被分配给左侧的变量。LHS 的结果值与 RHS 的值不同没有法律依据。我知道一些编译器在这方面存在错误。但某些东西是否存储在寄存器中应该与它无关。 (11认同)
  • 如果在第一行的寄存器中计算“x”,保持比“float”的最小值更高的精度,会怎么样?`y = x` 可能在内存中,仅保留 `float` 精度。然后,将使用内存与寄存器进行相等性测试,精度不同,因此无法保证。 (10认同)
  • @Voo:在 ISO C++ 中,任何赋值都应该四舍五入到类型宽度。在大多数针对 x87 的编译器中,只有当编译器决定溢出/重新加载时才会发生这种情况。您可以使用“gcc -ffloat-store”强制执行以严格遵守。但这个问题是关于`x=y; x==y;` 不对中间的任何一个变量做任何事情。**如果“y”已经四舍五入以适合浮点型,则转换为 double 或 long double 并返回不会改变该值。** ... (6认同)
  • `x+pow(b,2)==x+pow(a,3)` 可能不同于 `auto one=x+pow(b,2); 自动二=y+pow(a,3); one==two` 因为一个可能会使用比另一个更高的精度进行比较(如果 1/2 是 ram 中的 64 位值,而 intermediste 值是 fpu 上的 80 位)。所以有时候,作业可以起到一些作用。 (5认同)
  • 编译器标志怎么样?看来他们可以打破这个规则。例如,关于“/fp:fast”的 MSVC 文档写道:*“编译器可能会在赋值语句、类型转换或函数调用时省略舍入。”* (4认同)
  • @David 似乎假设 `=` 是根据某些特定的计算机体系结构而不是数学来实现的。再次,我承认有些编译器会错误地这样做。但是,如果有的话,他们就是不合规的。请记住,C++ 是一种抽象,而不是与 CPU 指令的一对一映射。 (2认同)
  • @kmdreko 我认为在答案中仍然值得一提。 (2认同)

Lig*_*ica 35

是的,y肯定会承担以下价值x

[expr.ass]/2: 在简单赋值 (=) 中,左操作数引用的对象通过用右操作数的结果替换其值来修改 ([defns.access])。

分配其他值没有余地。

(其他人已经指出,等价比较==仍然会评估falseNaN 值。)

浮点数的常见问题==是很容易没有您认为的价值。在这里,我们知道这两个值,无论它们是什么,都是相同的。

  • @ThomasWeller 这是一个不合规实现中的已知错误。值得一提的是! (7认同)