在C ++中,
double x = 1.0;
double y = x / 3.0;
if (x == y * 3.0)
cout << " They are equal!" ;
else
cout << " They are NOT equal." ;
将打印
‘They are NOT equal.’
如预期的那样,由于1/3作为有限尺寸尾数的(二进制)数的不精确表示。 但是在Python中(在https://repl.it/repls/MessyJaggedMouse上),
x = 1.0
y = x / 3.0
x == y * 3.0
打印
True
Python何时以及为何偏离上述预期行为?编辑:Python不会离开-请在下面查看我的答案。
答案 0 :(得分:6)
仅当我在C语言中使用x87数学时,这种情况才会发生。
正确舍入,使用IEEE 754双重算术,您将得到 true 。
但是,如果您以较高的精度计算中间值,则会得到 false 。不需要C来以64位精度计算所有中间值,并且在具有x87浮点指令的32位x86处理器上,最终将使用较大的80位浮点类型作为中间值。根据启用的优化设置和编译器的详细信息,计算将以不同的方式进行(使用不同的中间值),您将获得略有不同的结果。
#include <cstdio>
int main() {
double x = 1.0;
double y = x / 3.0;
std::printf("x == y * 3.0: %s\n", x == y * 3.0 ? "true" : "false");
return 0;
}
在GCC中,如果您使用false
进行编译,则会看到-mfpmath=387 -O0
。如果不使用true
进行编译(在AMD64上默认为SSE),或者如果使用-mfpmath=387
进行编译,则可以看到-O2
。
您可以在GodBolt上使用x87指令查看其编译方式:https://godbolt.org/z/rf1Rir-尝试添加-O2
或摆脱-mfpmath=387
,以查看它如何影响所生成的代码。
请注意,(1.0 / 3.0) * 3.0 == 1.0
有点巧合。您可以在Python中测试以下代码,例如:
1.0 / 49.0 * 49.0 == 1.0
这应该给False
。
答案 1 :(得分:0)
Python的浮子'使用double
精度52位的尾数:
> (1/3).hex()
‘0x1.5555555555555p-2’ # 13 5’s, 13*4==52
这是在二进制1/3:
1/3 == 0.0101010101...
52是存储偶数,因此尾数(第一1后部分)
010101...
以52位表示,因此以...0101
结尾。因此,ieee 754舍入不会改变52位尾数,而是乘以3或二进制11
进行倍增,得到0.11111...
并舍入到1
。因此
1.0 / 3.0 * 3.0 == 1.0
在ieee 754中,因此在@Dietrich指出的python和c ++ double
s中也是如此。
下面的代码允许探索为float
S的有23个比特(在我的系统)的故事,这是一个奇数...
请注意,四舍五入为23位会恢复为1,但是在float
之类的语句中从double
到3.0 * x == 1
的自动提升将阻止四舍五入为1。
include <iostream> using namespace std;
int main() {
float x = 1./3, y;
cout << hexfloat;
cout << x << endl; // 1./3. ieee rounded to 23 bits
cout << x * 3.0 << endl; // that times 3.0 exact, ie, promoted to double
cout << (double)x * 3. << endl; // same
cout << x * (float)3.0 << endl; // remains 23 bits, rounded to 1
y = 3.0 * x; // 0x1.0000008p+0 static_cast to 23 bit float
cout << y << endl; // again yields 1
cout << boolalpha; // Now in terms of equalities:
cout << (3.0*x==1.) << endl;
cout << ((float)3.*x==1.) << endl;
cout << (y==1.); return 0;
}
收益
0x1.555556p-2 // rounded to 1.0101...01011
0x1.0000008p+0 // that times 3.0
0x1.0000008p+0
0x1p+0 // now rounds to 1.00...
0x1p+0
false
true
true
锻炼; Tibial:探索49.0。 :)