私のコードは以下の通りです:
int main(int argc, char *argv[])
{
double f = 18.40;
printf("%d\n", (int)(10 * f));
return 0;
}
VC6.0 の結果は 184 ですが、Codeblock の結果は 183 です。なぜですか?
私のコードは以下の通りです:
int main(int argc, char *argv[])
{
double f = 18.40;
printf("%d\n", (int)(10 * f));
return 0;
}
VC6.0 の結果は 184 ですが、Codeblock の結果は 183 です。なぜですか?
これは、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
cvttsd2si
GCC を使用する場合は、フラグを使用してコンパイルすることにより、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
>
ポイントは、つまり0.4
です2/5
。1/3 が 10 進数として正確に表現できないのと同様に、分母が 2 の累乗以外の分数は浮動小数点数で正確に表現できません。したがって、コンパイラは近くの表現可能な数を選択する必要があり、結果10*18.4
は正確184
ではありませんが183.999
...
ここで、すべては float が整数に変換されるときに使用される丸めモードに依存します。最も近い値への丸めまたは無限大への丸めでは が得られ、184
ゼロへの丸めまたは負の無限大への丸めでは が得られます183
。
浮動小数点計算は、コンパイラやアーキテクチャによって実装方法が異なります。同じコンパイラでも、さまざまな結果をもたらすさまざまな操作モードを持つことができます。
たとえば、あなたのプログラムと私のインストールした 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
. しかし、推論には欠陥があり、表現可能性、丸めなどの問題を説明できていないと思います。