11

最近写了一个小程序,用2个不同版本的mingw32(on Windows8)编译。令人惊讶的是,我得到了两个不同的结果。我尝试拆卸它,但没有发现任何特别之处。有人可以帮我吗?谢谢你。

exe文件: https ://www.dropbox.com/s/69sq1ttjgwv1qm3/asm.7z

结果:720720(gcc 4.5.2版)、720719(gcc 4.7.0版)</p>

编译器标志:-lstdc++ -static

代码截取如下:

#include <iostream>
#include <cmath>

using namespace std;

int main()
{
    int a = 55440, b = 13;
    a *= pow(b, 1);
    cout << a << endl;
    return 0;
}

装配输出(4.5.2):

http://pastebin.com/EJAkVAaH

装配输出(4.7.0):

http://pastebin.com/kzbbFGs6

4

1 回答 1

9

我已经能够使用单个版本的编译器重现该问题。

我的是 MinGW g++ 4.6.2。

当我将程序编译为g++ -g -O2 bugflt.cpp -o bugflt.exe时,我得到720720.

这是反汇编main()

_main:
        pushl   %ebp
        movl    %esp, %ebp
        andl    $-16, %esp
        subl    $16, %esp
        call    ___main
        movl    $720720, 4(%esp)
        movl    $__ZSt4cout, (%esp)
        call    __ZNSolsEi
        movl    %eax, (%esp)
        call    __ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
        xorl    %eax, %eax
        leave
        ret

如您所见,该值是在编译时计算的。

当我将它编译为 时g++ -g -O2 -fno-inline bugflt.cpp -o bugflt.exe,我得到720719.

这是反汇编main()

_main:
        pushl   %ebp
        movl    %esp, %ebp
        andl    $-16, %esp
        subl    $32, %esp
        call    ___main
        movl    $1, 4(%esp)
        movl    $13, (%esp)
        call    __ZSt3powIiiEN9__gnu_cxx11__promote_2INS0_11__enable_ifIXaasrSt15__is_arithmeticIT_E7__valuesrS3_IT0_E7__valueES4_E6__typeES6_E6__typeES4_S6_
        fmuls   LC1
        fnstcw  30(%esp)
        movw    30(%esp), %ax
        movb    $12, %ah
        movw    %ax, 28(%esp)
        fldcw   28(%esp)
        fistpl  4(%esp)
        fldcw   30(%esp)
        movl    $__ZSt4cout, (%esp)
        call    __ZNSolsEi
        movl    $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
        movl    %eax, (%esp)
        call    __ZNSolsEPFRSoS_E
        xorl    %eax, %eax
        leave
        ret
...
LC1:
        .long   1196986368 // 55440.0 exactly

如果我将调用替换为exp()像这样加载 13.0:

_main:
        pushl   %ebp
        movl    %esp, %ebp
        andl    $-16, %esp
        subl    $32, %esp
        call    ___main
        movl    $1, 4(%esp)
        movl    $13, (%esp)

//        call    __ZSt3powIiiEN9__gnu_cxx11__promote_2INS0_11__enable_ifIXaasrSt15__is_arithmeticIT_E7__valuesrS3_IT0_E7__valueES4_E6__typeES6_E6__typeES4_S6_
        fildl    (%esp)

        fmuls   LC1
        fnstcw  30(%esp)
        movw    30(%esp), %ax
        movb    $12, %ah
        movw    %ax, 28(%esp)
        fldcw   28(%esp)
        fistpl  4(%esp)
        fldcw   30(%esp)
        movl    $__ZSt4cout, (%esp)
        call    __ZNSolsEi
        movl    $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
        movl    %eax, (%esp)
        call    __ZNSolsEPFRSoS_E
        xorl    %eax, %eax
        leave
        ret

我明白了720720

exp()如果我在如下fistpl 4(%esp)指令的持续时间内设置 x87 FPU 控制字的相同舍入和精度控制字段:

_main:
        pushl   %ebp
        movl    %esp, %ebp
        andl    $-16, %esp
        subl    $32, %esp
        call    ___main
        movl    $1, 4(%esp)
        movl    $13, (%esp)

        fnstcw  30(%esp)
        movw    30(%esp), %ax
        movb    $12, %ah
        movw    %ax, 28(%esp)
        fldcw   28(%esp)

        call    __ZSt3powIiiEN9__gnu_cxx11__promote_2INS0_11__enable_ifIXaasrSt15__is_arithmeticIT_E7__valuesrS3_IT0_E7__valueES4_E6__typeES6_E6__typeES4_S6_

        fldcw   30(%esp)

        fmuls   LC1
        fnstcw  30(%esp)
        movw    30(%esp), %ax
        movb    $12, %ah
        movw    %ax, 28(%esp)
        fldcw   28(%esp)
        fistpl  4(%esp)
        fldcw   30(%esp)
        movl    $__ZSt4cout, (%esp)
        call    __ZNSolsEi
        movl    $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
        movl    %eax, (%esp)
        call    __ZNSolsEPFRSoS_E
        xorl    %eax, %eax
        leave
        ret

我也明白720720

由此我只能得出结论,exp()计算 13 1不精确为 13.0。

可能值得查看它的源代码,__gnu_cxx::__promote_2<__gnu_cxx::__enable_if<(std::__is_arithmetic<int>::__value)&&(std::__is_arithmetic<int>::__value), int>::__type, int>::__type std::pow<int, int>(int, int)以确切了解它如何设法用整数搞砸求幂(请参阅,与 C 不同,exp()它需要两个ints而不是两个doubles)。

但我不会为此责备exp()。C++11 定义float pow(float, float)long double pow(long double, long double)C 的double pow(double, double). 但double pow(int, int)标准中没有。

编译器为整数参数提供版本这一事实并没有对结果的精度做出任何额外的保证。如果exp()计算 a b

    a b = 2 b * log 2 (a)

或作为

    a b = e b * ln(a)

对于浮点值,过程中肯定会存在舍入误差。

如果“整数”版本的exp()做类似的事情并由于舍入误差而导致类似的精度损失,它仍然可以正常工作。即使精度损失是由于一些愚蠢的错误而不是因为正常的舍入误差,它也会这样做。

无论这种行为看起来多么令人惊讶,它都是正确的。或者我相信直到被证明是错误的。

于 2013-03-10T11:39:23.660 回答