6

型のイプシロン値の近似値を計算しようとしていますfloat(そして、それが既に標準ライブラリにあることはわかっています)。

このマシンのイプシロン値は次のとおりです (概算で印刷されています)。

 FLT_EPSILON = 1.192093e-07
 DBL_EPSILON = 2.220446e-16
LDBL_EPSILON = 1.084202e-19

FLT_EVAL_METHOD2すべてがlong double正確に行われるので、 floatdoubleおよびlong doubleは 32、64、および 96 ビットです。

1 から始まり、値が小さくなりすぎるまで 2 で割った値の近似値を取得しようとしました。すべての操作をfloat次の型で行いました。

# include <stdio.h>

int main(void)
{
    float floatEps = 1;

    while (1 + floatEps / 2 != 1)
        floatEps /= 2;

    printf("float eps = %e\n", floatEps);
}

出力は私が探していたものではありません:

float epsilon = 1.084202e-19

中間操作は ( の値によりFLT_EVAL_METHOD) 最高の精度で行われるため、この結果は妥当と思われます。

ただし、これは次のとおりです。

// 2.0 is a double literal
while ((float) (1 + floatEps / 2.0) != 1)
    floatEps /= 2;

次の出力が得られます。これは正しいものです。

float epsilon = 1.192093e-07

しかし、これは:

// no double literals
while ((float) (1 + floatEps / 2) != 1)
    floatEps /= 2;

最初の結果のように、再び間違った結果につながります。

float epsilon = 1.084202e-19

これらの最後の 2 つのバージョンは、このプラットフォームでは同等であるはずですが、これはコンパイラのバグですか? そうでない場合、何が起こっていますか?

コードは次のようにコンパイルされます。

gcc -O0 -std=c99 -pedantic file.c

gcc のバージョンはかなり古いですが、私は大学生なので更新できません。

$ gcc -v
Using built-in specs.
Target: i486-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.4.5-8'
--with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs
--enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.4
--enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib
--libexecdir=/usr/lib --without-included-gettext --enable-threads=posix
--with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls
--enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc
--enable-targets=all --with-arch-32=i586 --with-tune=generic
--enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu
--target=i486-linux-gnu
Thread model: posix
gcc version 4.4.5 (Debian 4.4.5-8)

gcc の現在のバージョンである 4.7 は、自宅のコンピューターで正しく動作します。バージョンによって結果が異なるというコメントもあります。

いくつかの回答とコメントの後、期待どおりに動作しているものとそうでないものを明確にしたので、質問を少し変更して明確にしました。

4

2 に答える 2

7

コンパイラはfloat、任意のより大きな精度で式を評価することが許可されているため、最初の式が精度で評価されるように見えlong doubleます。2 番目の式では、結果を再度縮小することを強制しますfloat

いくつかの追加の質問と以下の議論への回答として、基本的に、浮動小数点型の 1 とのゼロ以外の最小の差を探しています。コンパイラの設定によっては、FLT_EVAL_METHOD関連する型よりも高い精度ですべての浮動小数点式を評価することを決定する場合があります。Pentium では伝統的に、浮動小数点ユニットの内部レジスタは 80 ビットであり、すべての小さな浮動小数点型にその精度を使用すると便利です。したがって、最終的にテストは compare の精度に依存します!=。明示的なキャストがない場合、この比較の精度は、コードではなくコンパイラによって決定されます。明示的なキャストを使用すると、比較を目的の型に縮小できます。

コンパイラが設定されていることを確認FLT_EVAL_METHODした2ので、浮動小数点計算に最高の精度を使用します。

FLT_EVAL_METHOD=2以下の議論の結論として、バージョン 4.5 より前のケースの実装に関連するバグがgccあり、少なくともバージョン 4.6 以降では修正されていると確信しています。2式で浮動小数点定数の代わりに整数定数が使用されている場合、生成されたアセンブリでは2.0キャスト tofloatが省略されます。-O1また、最適化レベルから、これらの古いコンパイラで正しい結果が生成されることも注目に値しますが、生成されるアセンブリはまったく異なり、少数の浮動小数点演算しか含まれていません。

于 2013-04-17T15:18:06.357 に答える
2

C99 C コンパイラは、実際の型よりも正確な浮動小数点型であるかのように、浮動小数点式を評価できます。

マクロFLT_EVAL_METHODは、戦略を示すためにコンパイラによって設定されます。

-1 不定;

0 は、すべての演算と定数を型の範囲と精度だけで評価します。

1 float 型と double 型の演算と定数を double 型の範囲と精度で評価し、long double 演算と定数を long double 型の範囲と精度で評価します。

2 すべての演算と定数を long double 型の範囲と精度で評価します。

歴史的な理由から、x86 プロセッサを対象とする場合の 2 つの一般的な選択肢は 0 と 2 です。

ファイルm.cは最初のプログラムです。コンパイラを使用してコンパイルすると、次のようになります。

$ gcc -std=c99 -mfpmath=387 m.c
$ ./a.out 
float eps = 1.084202e-19
$ gcc -std=c99  m.c
$ ./a.out 
float eps = 1.192093e-07

以下の他のプログラムをコンパイルすると、コンパイラはその動作に応じてマクロを設定します。

#include <stdio.h>
#include <float.h>

int main(){
  printf("%d\n", FLT_EVAL_METHOD);
}

結果:

$ gcc -std=c99 -mfpmath=387 t.c
$ ./a.out 
2
$ gcc -std=c99 t.c
$ ./a.out 
0
于 2013-04-17T15:44:33.053 に答える