Int to Float to Int转换精度损失

Zhe*_*hen 10 c++ floating-point assembly

最近,我编写了一个小程序,并使用2个不同版本的mingw32(在Windows8上)编译它.令人惊讶的是,我得到了两个不同的结果.我试图解除它,但没有发现什么特别的.谁能帮助我?谢谢.

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

结果:720720(gcc版本4.5.2),720719(gcc版本4.7.0)

编译器标志:-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;
}
Run Code Online (Sandbox Code Playgroud)

装配输出(4.5.2):

http://pastebin.com/EJAkVAaH

装配输出(4.7.0):

http://pastebin.com/kzbbFGs6

Ale*_*nze 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
Run Code Online (Sandbox Code Playgroud)

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

当我编译它时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
Run Code Online (Sandbox Code Playgroud)

如果我用这样的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
Run Code Online (Sandbox Code Playgroud)

我得到720720.

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

_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
Run Code Online (Sandbox Code Playgroud)

我也好720720.

由此我得出的结论exp()是,13 13并不是精确计算13 1.

可能值得查看它的源代码,__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()计算b

    a b = 2 b*log 2(a)

或者作为

    a b = e b*ln(a)

对于浮点值,过程中肯定会出现舍入错误.

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

不管这种行为看起来多么令人惊讶,这是正确的.或者我相信直到被证明是错误的.