メモの日々


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