11

最近、私は小さなプログラムを作成し、2つの異なるバージョンのmingw32(Windows8)を使用してコンパイルしました。驚いたことに、私は2つの異なる結果を得ました。分解してみましたが、特別なことは何も見つかりませんでした。誰か助けてもらえますか?ありがとうございました。

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

単一バージョンのコンパイラで問題を再現することができました。

私のはMinGWg++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)ような命令の場合と同じ期間、x87FPU制御ワードの丸めと精度の制御フィールドを設定すると次のようになります。

_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ます。

このことから、 131を13.0として正確にexp()計算していないと結論付けることしかできません。

そのソースコードを見て__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()2intsではなく2を必要としますdoubles)。

しかし、私はそれを責めませんexp()。C ++ 11は、Cに加えてを定義float pow(float, float)long double pow(long double, long double)ますdouble pow(double, double)。しかしdouble pow(int, int)、標準にはありません。

コンパイラが整数引数のバージョンを提供しているという事実は、結果の精度について追加の保証をするものではありません。abを次のようにexp()計算する場合

    a b = 2 b * log 2(a)

またはとして

    a b = e b * ln(a)

浮動小数点値の場合、プロセスで丸め誤差が発生する可能性があります。

の「整数」バージョンがexp()同様のことを行い、丸め誤差のために同様の精度の低下が発生した場合でも、それは正しく機能します。また、精度の低下が、通常の丸め誤差ではなく、いくつかのばかげたバグによるものであっても、それを実行します。

この振る舞いは意外に思われるかもしれませんが、それは正しいことです。またはそう私は間違っていることが証明されるまで信じています。

于 2013-03-10T11:39:23.660 に答える