问题 为什么在赋值后似乎改变了双倍的价值?


在我的机器上,以下程序的结果对我来说有点奇怪。

#include <iostream>

using namespace std;

int main(){
    double a = 20;
    double b = 0.020;
    double c = 1000.0;

    double d = b * c;

    if(a < b * c)
        cout << "a < b * c" << endl;

    if(a < d)
        cout << "a < d" << endl;

    return 0;
}

输出:

$ ./test
a < b * c

我知道双精度因精度而不准确。但是我不希望这个值发生变化并且给出不一致的比较结果。

如果 a < b * c 打印出来,我确实希望如此 a < d 也应该打印出来。但是当我在我的i686服务器甚至我的cygwin上运行此代码时。我可以看到 a < b * c 但是看不到 a < d

此问题已被确认为依赖于平台。这是由双重赋值的不同指令和实现引起的吗?

UPDATE

生成的程序集:

main:
.LFB1482:
    pushl   %ebp
.LCFI0:
    movl    %esp, %ebp
.LCFI1:
    subl    $56, %esp
.LCFI2:
    andl    $-16, %esp
    movl    $0, %eax
    subl    %eax, %esp
    movl    $0, -8(%ebp)
    movl    $1077149696, -4(%ebp)
    movl    $1202590843, -16(%ebp)
    movl    $1066695393, -12(%ebp)
    movl    $0, -24(%ebp)
    movl    $1083129856, -20(%ebp)
    fldl    -16(%ebp)
    fmull   -24(%ebp)
    fstpl   -32(%ebp)
    fldl    -16(%ebp)
    fmull   -24(%ebp)
    fldl    -8(%ebp)
    fxch    %st(1)
    fucompp
    fnstsw  %ax
    sahf
    ja  .L3
    jmp .L2

    //.L3 will call stdout

5158
2018-02-23 03:24


起源

也许它与持续优化有关。如果你抓住,你可以展示组件或重现它吗? a, b,和 c 从 std::cin? - Nate Kohl
编译器没有错误/警告。 - StarPinkER
我可以重现它如果我从std :: cin得到它,我会稍后发布程序集。 @NateKohl - StarPinkER
打印后确认该值相同。 @vdbuilder - StarPinkER
我没有得到x87和SSE代码的输出。您可以自己查看结果并编辑代码 coliru.stacked-crooked.com/a/3022f77c07303e32  你用的是什么编译器?你的机器AS3究竟是什么? - Z boson


答案:


假设:您可能会看到80位英特尔FPU的影响。

随着定义 double d = b * c, 数量 b * c 以80位精度计算,并在存储时舍入为64位 d(a < d) 将比较64位 a 到64位 d

OTOH,带着表达 (a < b * c),你有一个80位的算术结果 b * c 被直接比较 a 离开FPU之前。所以 b*c 结果永远不会通过保存在64位变量中来削减其精度。

您必须查看生成的指令以确定,并且我希望这会随编译器版本和优化程序标志而变化。


5
2018-02-23 03:57



看到 stackoverflow.com/q/20869904/420683 - dyp
我试过在这里设置x87 coliru.stacked-crooked.com/a/3022f77c07303e32 但它没有任何区别。我没有输出x87或SSE。这并不否定你所说的任何内容,但我只是好奇我必须设置哪些编译器标志才能看到效果。 - Z boson


我不确定AS3机器的硬件类型,但是,例如,您可以在内部浮点单元使用大于64位浮点数来存储中间结果的机器中看到此行为。在具有x87浮点单元的x86拱中就是这种情况(但不是SSE)。

问题是处理器将加载 b 和 c 到浮点寄存器,然后进行乘法并将临时结果存储在寄存器中。如果该寄存器大于64位,则结果将不同于 d (要么 a计算并存储回内存,强制它们为64位。

这是许多情况中的一种情况,您需要查看汇编代码以确定正在发生的事情。您还需要了解硬件如何在内部处理浮点计算。


3
2018-02-23 04:00



我认为你是对的。但这是否意味着在比较a和中间结果时,中间值不会被放入内存但是加载到fpu并在那里进行比较? - StarPinkER


在我的Windows机器上使用MinGW快速测试代码会产生完全相同的结果。但真正奇怪的是,如果我将双打更改为浮点数,那么一切都运行得很好(根本没有输出)。但是,如果我将它们更改为长双打,则会出现“a <b * c”和“a <d”。

我的猜测可能是因为双打应该允许更高的精度,在将两个立即值相乘并进行比较时会发生一些奇怪的事情,而不是将结果存储到以后?这也可以解释为什么最终问题也出现了长双打,因为它们需要更多的内存空间。


1
2018-02-23 06:37