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


167

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

float x = ...

float y = x;

assert(y == x)

既然y是从复制x,则断言是否正确?


78
让我向通过实际代码演示证明不平等的人提供50英镑的赏金。我想看看80 vs 64位的实际情况。再加上另一个50,用于解释所生成的汇编代码,该代码显示一个变量在寄存器中,而另一个不在寄存器中(或者不平等的原因可能是什么,我想在低级进行解释)。
Thomas Weller

1
@ThomasWeller关于此的GCC错误:gcc.gnu.org/bugzilla/show_bug.cgi ? id=323 ; 但是,我只是尝试在x86-64系统上对其进行复制,即使使用-ffast-math也不行。我怀疑您在32位系统上需要旧的GCC。
pjc50

5
@ pjc50:实际上,您需要一个80位系统来重现错误323;造成问题的是80x87 FPU。x86-64使用SSE FPU。多余的位会引起问题,因为在将值溢出到32位浮点数时会四舍五入。
MSalters

4
如果MSalters的理论是正确的(我怀疑是这样),则可以通过编译32位(-m32)或指示GCC使用x87 FPU(-mfpmath=387)进行复制。
科迪·格雷

4
将“ 48位”更改为“ 80位”,然后可以在其中删除@Hot的“神话”形容词。这正是您发表评论之前即将讨论的内容。x87(x86体系结构的FPU)使用80位寄存器,即“扩展精度”格式。
科迪·格雷

Answers:


125

除了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);
}

Godbolt演示: https

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

请参阅GCC常见问题解答参考:


2
在将float具有标准精度的a 与额外精度进行比较时会考虑额外的位,这似乎很奇怪。
纳特

13
@Nat 奇怪;这是一个错误
在轨道进行的轻度比赛

13
@ThomasWeller不,那是一个合理的奖项。尽管我想指出这一点,但这是不合规的行为
在轨道上进行的Lightness Races

4
我可以扩展这个答案,指出在汇编代码中到底发生了什么,并且这实际上违反了标准-尽管我不会称自己为语言律师,所以我不能保证不会晦涩难懂明确允许该行为的子句。我认为OP对实际编译器上的实际复杂性更感兴趣,而不是对完全没有错误的,完全兼容的编译器(事实上,不存在这种事实)。
chtz

4
值得一提的是,这-ffloat-store似乎是防止这种情况的方法。
OrangeDog

116

如果x为is NaN,则不会是正确的,因为on的NaN比较总是错误的(是,甚至NaN == NaN)。对于所有其他情况(正常值,次正常值,无穷大,零),此断言将为真。

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


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

除赋值和强制转换(除去所有额外的范围和精度)外,带有浮点操作数的运算值和需要进行常规算术转换的值以及浮点常量的值将求值为其范围和精度可能大于整数所要求的格式。类型。

但是,正如评论所指出的那样,某些情况下使用某些编译器,build-options和target 可能会使此矛盾。


10
如果x在第一行的寄存器中计算if ,则保持比a的最小值更高的精度float。该y = x可以在内存中,只保留float精度。然后,将针对寄存器对存储器进行相等性测试,精度不同,因此无法保证。
David Schwartz

5
x+pow(b,2)==x+pow(a,3)auto one=x+pow(b,2); auto two=y+pow(a,3); one==two可能与之不同,因为一个人可能比另一个人使用更高的精度进行比较(如果一个/两个是ram中的64位值,而中间值是fpu上的80位位)。因此,有时分配可以做些事情。
Yakk-Adam Nevraumont

22
@evg当然!我的回答只是遵循标准。如果您告诉编译器不存在冲突,那么所有赌注都将置为无效,尤其是在启用快速运算时。
kmdreko

11
@Voo请参阅我的答案中的报价。RHS的值分配给LHS上的变量。LHS的最终价值与RHS的价值没有法律依据。我赞赏几个编译器在这方面存在错误。但是,是否应该将某些内容存储在寄存器中与它无关。
在轨道进行的轻度比赛

6
@Voo:在ISO C ++中,应该在任何赋值上都将舍入为宽度。在大多数针对x87的编译器中,它实际上仅在编译器决定溢出/重新加载时才发生。您可以强制gcc -ffloat-store严格遵守。但是这个问题是关于x=y; x==y; 两者之间的任何变化都不做任何事情。 如果y已经舍入为适合浮点数的值,则转换为double或long double并返回不会更改该值。...
彼得·科德斯

34

是的,y肯定会采用x

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

没有其他分配值的余地。

(其他人已经指出,等效比较==仍将评估false为NaN值。)

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


7
@ThomasWeller这是因此不兼容的实现中的一个已知错误。值得一提的是!
在轨道进行的轻度比赛

起初,我认为用语言进行“价值”和“结果”之间的区分是不恰当的,但是C2.2、7.1.6的语言并没有要求没有区别。C3.3,7.1.6;您引用的标准草案的C4.2、7.1.6或C5.3、7.1.6。
埃里克塔

@EricTowers抱歉,您可以澄清这些参考文献吗?我找不到您要指向的内容
Orbit竞赛

@ LightnessRacesBY-SA3.0:CC2.2C3.3C4.2C5.3
Eric Towers

@EricTowers是的,仍然没有关注您。您的第一个链接转到附录C索引(什么都不告诉我)。接下来的四个链接都转到[expr]。如果我忽略链接并专注于引用,那么我会感到困惑,例如C.5.3似乎并没有解决术语“值”或“结果”的使用(尽管确实如此)在正常的英语环境中使用“结果”一次)。也许您可以更清楚地描述您认为标准在什么地方与众不同,并对此事件提供一个明确的引用。谢谢!
在轨道进行的轻度比赛

3

是的,在所有情况下(不考虑NaN和x87问题),这都是正确的。

如果memcmp对它们进行操作,则可以测试是否相等,同时可以比较NaN和sNaN。这也将要求编译器获取变量的地址,该地址会将值强制转换为32位float而不是80位。这将消除x87问题。这里的第二个断言旨在不表明==不会将NaNs比较为true:

#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。


1

在通常情况下,它将评估为true。(否则断言语句不会做任何事情)

编辑

“通常情况”是指排除其他用户指出的上述方案(例如NaN值和80x87浮点数单元)。

考虑到在当今情况下8087芯片已经过时,这个问题相当孤立,该问题适用于当前使用的浮点架构状态,除了NaN之外,所有情况都适用。

(参考约8087- https: //home.deec.uc.pt/~jlobo/tc/artofasm/ch14/ch143.htm

感谢@chtz再现了一个很好的例子,@ kmdreko提到了NaNs-以前不知道它们!


1
我认为从内存加载时完全有可能x位于浮点寄存器中y。内存的精度可能不如寄存器,导致比较失败。
David Schwartz

1
那可能是错误的一种情况,我还没有想到那么远。(由于OP没有提供任何特殊情况,因此我假设没有其他限制)
Anirban166

1
我真的不明白你在说什么。据我了解的问题,OP正在询问是否复制一个float并确保相等性测试成功。您的回答似乎是“是”。我在问为什么答案不是“否”。
David Schwartz

6
编辑使此答案不正确。C ++标准要求赋值将值转换为目标类型-可能在表达式求值中使用过高的精度,但可能无法通过赋值保留。将该值保存在寄存器还是存储器中并不重要。C ++标准在编写代码时要求它是一个float没有额外精度的值。
埃里克·波斯特皮希尔

2
@AProgrammer鉴于一个(n个)极端错误的编译器在理论上int a=1; int b=a; assert( a==b );可以引发一个断言,我认为只有在正确运行一个编译器的情况下回答这个问题才有意义(尽管可能会注意到某些版本的编译器确实/具有-知道-弄错了)。实际上,如果出于某种原因,编译器没有从寄存器存储的赋值结果中消除额外的精度,则应在使用该值之前这样做。
TripeHound

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.