简体   繁体   English

C ++:双精度,精度,虚拟机和GCC

[英]C++: doubles, precision, virtual machines and GCC

I have the following piece of code: 我有以下代码:

#include <cstdio>
int main()
{
   if ((1.0 + 0.1) != (1.0 + 0.1))
      printf("not equal\n");
    else
      printf("equal\n");
    return 0;
}

When compiled with O3 using gcc (4.4,4.5 and 4.6) and run natively (ubuntu 10.10), it prints the expected result of "equal". 当使用gcc(4.4,4.5和4.6)使用O3编译并本机运行(ubuntu 10.10)时,它会打印预期的“相等”结果。

However the same code when compiled as described above and run on a virtual machine (ubuntu 10.10, virtualbox image), it outputs "not equal" - this is the case for when the O3 and O2 flags are set however not O1 and below. 但是,如上所述编译并在虚拟机(ubuntu 10.10,virtualbox映像)上运行时,它输出“不相等” - 这是O3和O2标志设置但不是O1及以下的情况。 When compiled with clang (O3 and O2) and run upon the virtual machine I get the correct result. 当使用clang(O3和O2)编译并在虚拟机上运行时,我得到了正确的结果。

I understand 1.1 can't be correctly represented using double and I've read "What Every Computer Scientist Should Know About Floating-Point Arithmetic" so please don't point me there, this seems to be some kind of optimisation that GCC does that somehow doesn't seem to work in virtual machines. 我理解1.1无法使用double正确表示,我读过“每个计算机科学家应该知道浮点算术的内容”所以请不要指向我那里,这似乎是GCC做的某种优化不知何故似乎在虚拟机中不起作用。

Any ideas? 有任何想法吗?

Note: The C++ standard says type promotion in this situations is implementation dependent, could it be that GCC is using a more precise internal representation that when the inequality test is applied holds true - due to the extra precision? 注意:C ++标准说在这种情况下类型提升是依赖于实现的,可能是GCC使用更精确的内部表示,当应用不等式测试时,它是否正确 - 由于额外的精度?

UPDATE1: The following modification of the above piece of code, now results in the correct result. UPDATE1:以上修改上面的代码,现在得到了正确的结果。 It seems at some point, for whatever reason, GCC turns off floating point control word. 在某些时候,无论出于何种原因,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: For those asking about the const expression nature of the code, I've changed it as follows, and still fails when compiled with GCC. UPDATE2:对于那些询问代码的const表达性质的人,我已经改变了如下,并且在使用GCC编译时仍然失败。 - but i assume the optimizer may be turning the following into a const expression too. - 但我认为优化器也可能将以下内容转换为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 Resolution: Upgrading virtualbox to version 4.1.8r75467 resolved the issue. UPDATE3解决方案:将virtualbox升级到版本4.1.8r75467解决了该问题。 However the their remains one issue, that is: why was the clang build working. 然而,它们仍然是一个问题,那就是:为什么clang构建起作用。

UPDATE: See this posting How to deal with excess precision in floating-point computations? 更新:请参阅此文章如何处理浮点计算中的过度精度? It address the issues of extended floating point precision. 它解决了扩展浮点精度的问题。 I forgot about the extended precision in x86. 我忘记了x86中的扩展精度。 I remember a simulation that should have been deterministic, but gave different results on Intel CPUs than on PowePC CPUs. 我记得一个应该是确定性的模拟,但是在Intel CPU上比在PowePC CPU上给出了不同的结果。 The causes was Intel's extended precision architecture. 原因是英特尔扩展的精密架构。

This Web page talks about how to throw Intel CPUs into double-precision rounding mode: http://www.network-theory.co.uk/docs/gccintro/gccintro_70.html . 该网页讨论了如何将Intel CPU投入双精度舍入模式: http//www.network-theory.co.uk/docs/gccintro/gccintro_70.html


Does virtualbox guarantee that its floating point operations are identical to the the hardware's floating point operations? virtualbox是否保证其浮点运算与硬件的浮点运算相同? I could not find a guarantee like that with a quick Google search. 通过快速谷歌搜索,我无法找到这样的保证。 I also did not find a promise that vituralbox FP ops conform to IEEE 754. 我也没有发现vituralbox FP ops符合IEEE 754的承诺。

VMs are emulators that try-- and mostly succeed-- to the emulate a particular instruction set or architecture. 虚拟机是模拟器,可以模拟特定的指令集或体系结构,并且大多数成功。 They are just emulators, however, and subject to their own implementation quirks or design issues. 然而,它们只是模拟器,并且受制于它们自己的实现怪癖或设计问题。

If you haven't already, post the question forums.virtualbox.org and see what the community says about it. 如果您还没有,请发布问题forums.virtualbox.org并查看社区对此的评价。

Yep that is really strange behavior, but it can actually easily be explained: 是的,这是非常奇怪的行为,但实际上可以很容易地解释:

On x86 floating point registers internally use more precision (eg 80 instead of 64). 在x86浮点寄存器内部使用更高的精度(例如80而不是64)。 This means the computation 1.0 + 0.1 will be computed with more precision (and since 1.1 can't be represented exactly in binary at all those extra bits WILL be used) in the registers. 这意味着计算1.0 + 0.1将在寄存器中以更高的精度计算(并且因为1.1在所有那些额外的位将被使用时不能精确地表示为二进制)。 Only when storing the result to memory will it be truncated. 仅当将结果存储到存储器时才会被截断。

What this means is simple: If you compare a value loaded from memory with a value newly computed in the registers you'll get a "non-equal" back, because one value was truncated while the other wasn't. 这意味着什么很简单:如果你将从内存加载的值与在寄存器中新计算的值进行比较,你会得到一个“不相等”的回,因为一个值被截断而另一个没有。 So that has nothing to do with VM/no VM it just depends on the code the compiler generates which can easily fluctuate as we see there. 因此,这与VM /无VM无关,它只取决于编译器生成的代码,这些代码很容易随着我们看到的那样波动。

Add it to the growing list of floating point surprises.. 将它添加到不断增加的浮点惊喜列表中..

I can confirm the same behaviour of your non-VM code, but since I don't have a VM I haven't tested the VM part. 我可以确认您的非VM代码的相同行为,但由于我没有VM,我没有测试VM部分。

However, the compiler, both Clang and GCC will evaluate the constant expression at compile time. 但是,编译器,Clang和GCC都将在编译时评估常量表达式。 See the assembly output below (using gcc -O0 test.cpp -S ) : 请参阅下面的程序集输出(使用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

It looks like you understand assembly, but it's clear that there is only the "equal" string, there is no "not equal". 看起来你理解汇编,但很明显只有“相等”的字符串,没有“不相等”。 So the comparison is not even done at run time, it just prints "equal". 因此,甚至在运行时都没有进行比较,只是打印“相等”。

I would try to code the calculation and comparison using assembly and see if you have the same behavior. 我会尝试使用程序集对计算和比较进行编码,看看你是否有相同的行为。 If you have different behavior on the VM, then it's the way the VM does the calculation. 如果您在VM上有不同的行为,那么它就是VM进行计算的方式。

UPDATE 1: (Based on the "UPDATE 2" in the original question). 更新1 :(基于原始问题中的“更新2”)。 Below is the gcc -O0 -S test.cpp output assembly (for 64 bit architecture). 下面是gcc -O0 -S test.cpp输出组件(用于64位架构)。 In it you can see the movabsq $4607182418800017408, %rax line twice. 在其中您可以看到movabsq $4607182418800017408, %rax行两次。 This will be for the two comparison flags, I haven't verified, but I presume the $4607182418800017408 value is 1.1 in floating point terms. 这将是两个比较标志,我还没有验证,但我认为$ 4607182418800017408的值是浮点数1.1。 It would be interesting to compile this on the VM, if you get the same result (two similar lines) then the VM will be doing something funny at run-time, otherwise it's a combination of VM and compiler. 在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

I see you added another question: 我看到你添加了另一个问题:

Note: The C++ standard says type promotion in this situations is implementation dependent, could it be that GCC is using a more precise internal representation that when the inequality test is applied holds true - due to the extra precision? 注意:C ++标准说在这种情况下类型提升是依赖于实现的,可能是GCC使用更精确的内部表示,当应用不等式测试时,它是否正确 - 由于额外的精度?

The answer to that one is no. 那个问题的答案是否定的。 1.1 is not exactly representable in a binary format, no matter how many bits the format has. 无论格式有多少位, 1.1都不能以二进制格式表示。 You can get close, but not with an infinite number of zeros after the .1 . 你可以靠近,但不能在.1之后使用无限数量的零。

Or did you mean an entirely new internal format for decimals? 或者你的意思是一个全新的小数内部格式? No, I refuse to believe that. 不,我拒绝相信。 It wouldn't be very compatible if it did. 如果它这样做就不会很兼容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM