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; }