3

私のコードは以下の通りです:

int main(int argc, char *argv[])
{

    double f = 18.40;
    printf("%d\n", (int)(10 * f));

    return 0;
}

VC6.0 の結果は 184 ですが、Codeblock の結果は 183 です。なぜですか?

4

4 に答える 4

8

これは、GCC がコードを CPU の古いアーキテクチャと可能な限り後方互換にしようとするのに対し、MSVC はアーキテクチャの新しい未来を利用しようとするためです。

MSVC によって生成されたコードは、2 つの数値 10.0 × 18.40 を乗算します。

.text:00401006 fld     ds:dbl_40D168
.text:0040100C fstp    [ebp+var_8]
.text:0040100F fld     ds:dbl_40D160
.text:00401015 fmul    [ebp+var_8]
.text:00401018 call    __ftol2_sse

次に、 という名前の関数を呼び出します。__ftol2_sseこの関数内で、 という名前の命令を使用して結果を整数に変換しますcvttsd2si

.text:00401189 push    ebp
.text:0040118A mov     ebp, esp
.text:0040118C sub     esp, 8
.text:0040118F and     esp, 0FFFFFFF8h
.text:00401192 fstp    [esp+0Ch+var_C]
.text:00401195 cvttsd2si eax, [esp+0Ch+var_C]
.text:0040119A leave
.text:0040119B retn

この指示は、このページcvttsd2siに従っています:

スカラー倍精度浮動小数点値 (切り捨てあり) をクワッドワード整数の符号付きダブルワード (SSE2) に変換します

基本的に倍精度を整数に変換します。この命令は、Intel Pentium 4 で導入されたSSE2と呼ばれる命令セットの一部です。

GCC はデフォルトで設定されたこの命令を使用せず、i386 から利用可能な命令でそれを実行しようとします:

fldl   0x28(%esp)
fldl   0x403070
fmulp  %st,%st(1)
fnstcw 0x1e(%esp)
mov    0x1e(%esp),%ax
mov    $0xc,%ah
mov    %ax,0x1c(%esp)
fldcw  0x1c(%esp)
fistpl 0x18(%esp)
fldcw  0x1e(%esp)
mov    0x18(%esp),%eax
mov    %eax,0x4(%esp)
movl   $0x403068,(%esp)
call   0x401b44 <printf>
mov    $0x0,%eax

cvttsd2siGCC を使用する場合は、フラグを使用してコンパイルすることにより、SSE2 から利用可能なフューチャーを使用するようにGCC に指示する必要があります-msse2が、これは、古いコンピューターをまだ使用している一部の人々がこのプログラムを実行できないことも意味します。その他のオプションについては、Intel 386 および AMD x86-64 オプションを参照してください。

したがって、コンパイルした後、結果を 32 ビット整数に変換するために-msse2使用します。cvttsd2si

0x004013ac <+32>:    movsd  0x18(%esp),%xmm1
0x004013b2 <+38>:    movsd  0x403070,%xmm0
0x004013ba <+46>:    mulsd  %xmm1,%xmm0
0x004013be <+50>:    cvttsd2si %xmm0,%eax
0x004013c2 <+54>:    mov    %eax,0x4(%esp)
0x004013c6 <+58>:    movl   $0x403068,(%esp)
0x004013cd <+65>:    call   0x401b30 <printf>
0x004013d2 <+70>:    mov    $0x0,%eax

これで、MSVC と GCC の両方が同じ番号を与えるはずです:

> type test.c
#include <stdio.h>

int main(int argc, char *argv[])
{

    double f = 18.40;
    printf("%d\n", (int) (10.0  * f));

    return 0;
}
> gcc -Wall test.c -o gcctest.exe -msse2
> cl test.c /W3 /link /out:msvctest.exe
> gcctest.exe
184
> msvctest.exe
184
>
于 2013-09-03T11:02:27.623 に答える
2

ポイントは、つまり0.4です2/5。1/3 が 10 進数として正確に表現できないのと同様に、分母が 2 の累乗以外の分数は浮動小数点数で正確に表現できません。したがって、コンパイラは近くの表現可能な数を選択する必要があり、結果10*18.4は正確184ではありませんが183.999...

ここで、すべては float が整数に変換されるときに使用される丸めモードに依存します。最も近い値への丸めまたは無限大への丸めでは が得られ、184ゼロの丸めまたは負の無限大への丸めでは が得られます183

于 2013-09-03T08:44:14.850 に答える
1

浮動小数点計算は、コンパイラやアーキテクチャによって実装方法が異なります。同じコンパイラでも、さまざまな結果をもたらすさまざまな操作モードを持つことができます。

たとえば、あなたのプログラムと私のインストールした gcc (MinGW、4.6.2) を次のようにコンパイルするとします。

gcc main.c

出力は、レポートのように 183 です。

ただし、次のようにコンパイルすると:

gcc main.c -ffloat-store

出力は 184 です。

違いを本当に理解したい場合は、正確なコンパイラ バージョンを指定し、コンパイラに渡すオプションを指定する必要があります。

18.4より基本的には、値を 2 進浮動小数点値として正確に表すことはできないことに注意する必要があります。最も近い表現可能な倍精度値18.4は次のとおりです。

18.39999 99999 99998 57891 45284 79799 62825 77514 64843 75

したがって、プログラムからの正しい出力は184. しかし、推論には欠陥があり、表現可能性、丸めなどの問題を説明できていないと思います。

于 2013-09-03T09:26:19.097 に答える