我有以下代码:
#include <cstdio>
int main()
{
if ((1.0 + 0.1) != (1.0 + 0.1))
printf("not equal\n");
else
printf("equal\n");
return 0;
}
当使用gcc(4.4,4.5和4.6)使用O3编译并本机运行(ubuntu 10.10)时,它会打印&#34;等于&#34;的预期结果。
但是,如上所述编译并在虚拟机(ubuntu 10.10,virtualbox image)上运行时,相同的代码输出&#34;不等于&#34; - 这是O3和O2标志设置但不是O1和更低的情况。当使用clang(O3和O2)编译并在虚拟机上运行时,我得到了正确的结果。
我理解1.1无法使用double正确表示,并且我已经阅读了&#34;每个计算机科学家应该知道的关于浮点算术的内容&#34; 所以请不要指向我,这似乎是GCC所做的某种优化,它似乎在虚拟机中似乎不起作用。
有什么想法吗?
注意:C ++标准说在这种情况下的类型提升是依赖于实现的,可能是GCC使用更精确的内部表示,当应用不等式测试时,由于额外的精度,它是否正确?
UPDATE1:以上对上述代码的修改,现在可以得到正确的结果。看来在某种程度上,无论出于何种原因,GCC都会关闭浮点控制字。
#include <cstdio>
void set_dpfpu() { unsigned int mode = 0x27F; asm ("fldcw %0" : : "m" (*&mode));
int main()
{
set_dpfpu();
if ((1.0 + 0.1) != (1.0 + 0.1))
printf("not equal\n");
else
printf("equal\n");
return 0;
}
UPDATE2:对于那些询问代码的const表达性质的人,我已经按照以下方式对其进行了更改,并且在使用GCC编译时仍然失败。 - 但我认为优化器也可能将以下内容转换为const表达式。
#include <cstdio>
void set_dpfpu() { unsigned int mode = 0x27F; asm ("fldcw %0" : : "m" (*&mode));
int main()
{
//set_dpfpu(); uncomment to make it work.
double d1 = 1.0;
double d2 = 1.0;
if ((d1 + 0.1) != (d2 + 0.1))
printf("not equal\n");
else
printf("equal\n");
return 0;
}
UPDATE3解决方案:将virtualbox升级到版本4.1.8r75467解决了此问题。然而,他们仍然是一个问题,那就是:为什么clang构建起作用。
答案 0 :(得分:10)
更新:查看此帖子How to deal with excess precision in floating-point computations? 它解决了扩展浮点精度的问题。我忘记了x86中的扩展精度。我记得一个应该是确定性的模拟,但是在Intel CPU上比在PowePC CPU上给出了不同的结果。原因是英特尔的扩展精度架构。
此网页讨论如何将Intel CPU投入双精度舍入模式:http://www.network-theory.co.uk/docs/gccintro/gccintro_70.html。
<小时/> virtualbox是否保证其浮点运算与硬件的浮点运算相同?通过快速谷歌搜索,我无法找到这样的保证。我也没有发现vituralbox FP ops符合IEEE 754的承诺。
虚拟机是模拟器,可以模拟特定的指令集或体系结构,并且大多数成功。然而,它们只是模拟器,并且受制于它们自己的实现怪癖或设计问题。
如果您还没有,请发布问题forums.virtualbox.org并查看社群对此的评价。
答案 1 :(得分:5)
是的,这是一种非常奇怪的行为,但实际上可以很容易地解释:
在x86浮点寄存器内部使用更高的精度(例如80而不是64)。这意味着计算1.0 + 0.1
将在寄存器中以更高的精度计算(并且因为在所有那些将要使用的额外位,所以1.1不能精确地以二进制表示)。只有在将结果存储到内存时才会被截断。
这意味着什么是简单的:如果将从内存加载的值与在寄存器中新计算的值进行比较,则会得到“不相等”的回复,因为一个值被截断而另一个值没有。因此,它与VM /无VM无关,它只取决于编译器生成的代码,这些代码很容易随着我们的目的而波动。
将它添加到不断增加的浮点惊喜列表中。
答案 2 :(得分:4)
我可以确认您的非VM代码的相同行为,但由于我没有VM,因此我没有测试VM部分。
然而,编译器,Clang和GCC都将在编译时评估常量表达式。请参阅下面的装配输出(使用gcc -O0 test.cpp -S
):
.file "test.cpp"
.section .rodata
.LC0:
.string "equal"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
.section .note.GNU-stack,"",@progbits
看起来你理解汇编,但很明显只有“相等”的字符串,没有“不相等”。因此,甚至在运行时都没有进行比较,它只是打印“相等”。
我会尝试使用程序集对计算和比较进行编码,看看你是否有相同的行为。如果您在VM上有不同的行为,那么它就是VM进行计算的方式。
更新1:(基于原始问题中的“更新2”)。下面是gcc -O0 -S test.cpp
输出程序集(对于64位体系结构)。在其中,您可以看到movabsq $4607182418800017408, %rax
行两次。这将是两个比较标志,我还没有验证,但我认为$ 4607182418800017408的值是浮点数1.1。在VM上编译它会很有趣,如果你得到相同的结果(两个相似的行),那么VM将在运行时做一些有趣的事情,否则它是VM和编译器的组合。
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movabsq $4607182418800017408, %rax
movq %rax, -16(%rbp)
movabsq $4607182418800017408, %rax
movq %rax, -8(%rbp)
movsd -16(%rbp), %xmm1
movsd .LC1(%rip), %xmm0
addsd %xmm1, %xmm0
movsd -8(%rbp), %xmm2
movsd .LC1(%rip), %xmm1
addsd %xmm2, %xmm1
ucomisd %xmm1, %xmm0
jp .L6
ucomisd %xmm1, %xmm0
je .L7
答案 3 :(得分:2)
我看到你又添了一个问题:
注意:C ++标准说在这种情况下的类型提升是依赖于实现的,可能是GCC使用更精确的内部表示,当应用不等式测试时,由于额外的精度,它是否正确?
那个问题的答案是否定的。无论格式有多少位,1.1
都不能以二进制格式表示。您可以在.1
之后接近,但不能使用无限数量的零。
或者你的意思是一个全新的小数内部格式?不,我拒绝相信。如果它这样做就不会很兼容。