2012年11月11日(日) [長年日記]
■ [c][dev] x86での浮動小数点演算
x86上のGCCでコンパイルしたプログラムの動作が、x64上のそれと異なっていた。調べると、両者の浮動小数点演算のデフォルトの振る舞いが違うみたい。gccのmanに次のようにある。
-mfpmath=unit
選択されたユニット unit のための浮動小数点演算を生成します。unit た
めの選択は次の通りです:
387 チップの大多数に存在する標準の 387 浮動小数点コプロセッサを使用
し、そのほかは、エミュレートされます。このオプションつきでコン
パイルされたコードは、ほとんどすべての点で動作します。一時的な
結果は、他のチップの大部分と比べて、わずかに異なった結果をもた
らすタイプによって指定された精度の代わりに 80 ビットの精度で計
算されます。より詳細な記述については -ffloat-store を参照してく
ださい。
これは i386 コンパイラのためのデフォルトの選択です。
sse SSE 命令セットに存在するスカラ浮動小数点命令を使用します。この
命令セットは Pentium3 以降、Athlon-4, Athlon-xp と Athlon-mp
チップによる AMD 系列でサポートされます。SSE 命令セットの初期の
バージョンは、単精度演算のみをサポートし、そのため、倍精度と拡
張精度演算は、まだ 387 を使用して行われます。後のバージョン、
Pentium4 でのみ存在し、将来の AMD x86-64 チップは、倍精度演算も
サポートしています。
i386 コンパイラに関して、利用者は、SSE 拡張を有効にして、このオ
プションが効果があるようにするために、-march=cpu-type, -msse ま
たは -msse2 スイッチを使用する必要があります。x86-64 コンパイラ
について、これらの拡張はデフォルトで有効にされています。
結果のコードは、大多数の場合にかなり速くなり、387 コードの数値
不安定性問題を避けるべきですが、一時的なものを 80 ビットと期待
するいくつかの既存のコードを壊すかもしれません。
これは x86-64 コンパイラのためのデフォルトの選択です。
なので、x86上だとデフォルトで「80 ビットの精度で計算」され、x64上だと80ビットでは計算されないようだ。80ビットでの計算のついてはBinary HacksのHACK#98「x86が持つ浮動小数点演算命令の特殊性」でも触れられていた。
上の引用を参考に、x86上でのコンパイル時に「-mfpmath=sse -msse2」というオプションを付けるようにしたらx64と同じ振る舞いになるようになった。以下サンプルコード。
同一引数の関数呼び出し結果が不一致
次のプログラムをx86上のGCC 4.6.3でオプションなしでコンパイルすると、「not equal」が出力される。えー。
「-mfpmath=sse -msse2」オプションを付ければめでたく「equal」と出力され、これはx64上での動作と一致する。
#include <stdio.h>
double f(double d) { return d + 1.0; }
int main() {
if (f(0.1) == f(0.1)) {
printf("equal\n");
} else {
printf("not equal\n");
}
return 0;
}
doubleからintへのキャスト
次のプログラムをx86上のGCC 4.6.3でオプションなしでコンパイルすると
0 1 2 2 4 5 5 6 8 9
と出力され残念な感じ。「-mfpmath=sse -msse2」オプションを付けると
0 1 2 3 4 5 6 7 8 9
と出力される。
#include <stdio.h>
int main() {
int i;
for (i = 0; i < 10; ++i) {
double d = i / 10.0;
int j = d * 10.0;
printf("%i ", j);
}
printf("\n");
return 0;
}
[ツッコミを入れる]