21

我有以下代码:

#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)时,它会打印“等于”的预期结果。

但是,当按照上述方式编译并在虚拟机(ubuntu 10.10,virtualbox 映像)上运行相同的代码时,它会输出“不等于” - 这是设置 O3 和 O2 标志但未设置 O1 及以下的情况。当使用 clang(O3 和 O2)编译并在虚拟机上运行时,我得到了正确的结果。

我了解 1.1 无法使用 double 正确表示,并且我已阅读“每个计算机科学家应该了解的关于浮点运算的知识”,所以请不要指点我,这似乎是 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 构建工作。

4

4 回答 4

10

更新:请参阅这篇文章如何处理浮点计算中的超额精度? 它解决了扩展浮点精度的问题。我忘记了 x86 中的扩展精度。我记得有一个模拟应该是确定性的,但在 Intel CPU 上给出的结果与在 PowePC CPU 上的结果不同。原因是英特尔的扩展精度架构。

此网页讨论如何将 Intel CPU 置于双精度舍入模式: http: //www.network-theory.co.uk/docs/gccintro/gccintro_70.html


virtualbox 是否保证其浮点运算与硬件的浮点运算相同?通过快速谷歌搜索,我找不到这样的保证。我也没有找到 vituralbox FP ops 符合 IEEE 754 的承诺。

VM 是尝试(并且大部分成功)模拟特定指令集或体系结构的模拟器。然而,它们只是模拟器,并且受制于自己的实现怪癖或设计问题。

如果您还没有,请发布问题 forums.virtualbox.org 并查看社区对此的看法。

于 2012-01-18T20:13:15.860 回答
5

是的,这确实是一种奇怪的行为,但实际上可以很容易地解释:

在 x86 浮点寄存器内部使用更高的精度(例如 80 而不是 64)。这意味着计算1.0 + 0.1将在寄存器中以更高的精度进行计算(并且由于 1.1 不能以二进制精确表示,因此将使用所有这些额外的位)。只有将结果存储到内存时才会被截断。

这意味着什么很简单:如果您将从内存加载的值与在寄存器中新计算的值进行比较,您将得到一个“不相等”的结果,因为一个值被截断而另一个没有。所以这与 VM/no VM 无关,它只取决于编译器生成的代码,正如我们在那里看到的那样,它很容易波动。

将其添加到不断增长的浮点惊喜列表中。

于 2012-01-18T20:55:06.283 回答
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
于 2012-01-18T20:38:41.513 回答
2

我看到你添加了另一个问题:

注意:C++ 标准说在这种情况下类型提升是依赖于实现的,可能是 GCC 使用了更精确的内部表示,当应用不等式测试时它是正确的 - 由于额外的精度?

答案是否定的。1.1无论格式有多少位,都不能完全以二进制格式表示。您可以接近,但不能在.1.

还是您的意思是一种全新的小数内部格式?不,我拒绝相信。如果是这样,那将不是很兼容。

于 2012-01-18T20:26:35.683 回答