IEEE の float と double が等しいかどうかを比較する
-
09-06-2019 - |
質問
IEEE の float 型と double 型が等しいかどうかを比較するための最良の方法は何ですか?いくつかの方法について聞いたことがありますが、コミュニティがどのように考えているかを知りたかったのです。
解決
私が考える最良のアプローチは比較することです ULP.
bool is_nan(float f)
{
return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) == 0x7f800000 && (*reinterpret_cast<unsigned __int32*>(&f) & 0x007fffff) != 0;
}
bool is_finite(float f)
{
return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) != 0x7f800000;
}
// if this symbol is defined, NaNs are never equal to anything (as is normal in IEEE floating point)
// if this symbol is not defined, NaNs are hugely different from regular numbers, but might be equal to each other
#define UNEQUAL_NANS 1
// if this symbol is defined, infinites are never equal to finite numbers (as they're unimaginably greater)
// if this symbol is not defined, infinities are 1 ULP away from +/- FLT_MAX
#define INFINITE_INFINITIES 1
// test whether two IEEE floats are within a specified number of representable values of each other
// This depends on the fact that IEEE floats are properly ordered when treated as signed magnitude integers
bool equal_float(float lhs, float rhs, unsigned __int32 max_ulp_difference)
{
#ifdef UNEQUAL_NANS
if(is_nan(lhs) || is_nan(rhs))
{
return false;
}
#endif
#ifdef INFINITE_INFINITIES
if((is_finite(lhs) && !is_finite(rhs)) || (!is_finite(lhs) && is_finite(rhs)))
{
return false;
}
#endif
signed __int32 left(*reinterpret_cast<signed __int32*>(&lhs));
// transform signed magnitude ints into 2s complement signed ints
if(left < 0)
{
left = 0x80000000 - left;
}
signed __int32 right(*reinterpret_cast<signed __int32*>(&rhs));
// transform signed magnitude ints into 2s complement signed ints
if(right < 0)
{
right = 0x80000000 - right;
}
if(static_cast<unsigned __int32>(std::abs(left - right)) <= max_ulp_difference)
{
return true;
}
return false;
}
同様のテクニックをダブルスにも使用できます。コツは、(整数のように) 順序付けされるように浮動小数点を変換し、それらがどのように異なるかを確認することです。
なぜこのいまいましいことが私のアンダースコアを台無しにするのかわかりません。編集:ああ、おそらくそれはプレビューの単なるアーティファクトです。それなら大丈夫です。
他のヒント
私が使用している現在のバージョンはこれです
bool is_equals(float A, float B,
float maxRelativeError, float maxAbsoluteError)
{
if (fabs(A - B) < maxAbsoluteError)
return true;
float relativeError;
if (fabs(B) > fabs(A))
relativeError = fabs((A - B) / B);
else
relativeError = fabs((A - B) / A);
if (relativeError <= maxRelativeError)
return true;
return false;
}
これは、相対的および絶対的エラー許容度を組み合わせることにより、ほとんどの問題に対処するようです。ULP アプローチの方が優れていますか?もしそうなら、なぜですか?
@ドクターピザ:私はパフォーマンスの専門家ではありませんが、(ほとんどの場合) 固定小数点演算は浮動小数点演算よりも高速であると予想しています。
それはむしろ、あなたが彼らと何をしているかによって決まります。IEEE 浮動小数点と同じ範囲を持つ固定小数点型は、何倍も遅くなります (そして何倍も大きくなります)。
フロートに適したもの:
3Dグラフィックス、物理/工学、シミュレーション、気候シミュレーション....
数値ソフトウェアでは、2 つの浮動小数点数が正しいかどうかをテストしたいことがよくあります。 その通り 等しい。LAPACK にはそのような事例が豊富にあります。確かに、最も一般的なケースは、浮動小数点数が「0」、「1」、「2」、「半分」に等しいかどうかをテストする場合です。興味のある人がいたら、いくつかのアルゴリズムを選んで、さらに詳しく説明することができます。
また、BLAS では、浮動小数点数が正確に 0 であるか 1 であるかを確認したいことがよくあります。たとえば、ルーチン dgemv は次の形式の操作を計算できます。
- y = ベータ*y + アルファ*A*x
- y = ベータ*y + アルファ*A^T*x
- y = ベータ*y + アルファ*A^H*x
したがって、ベータが 1 に等しい場合は「プラスの割り当て」があり、ベータがゼロに等しい場合は「単純な割り当て」になります。したがって、これらの (一般的な) ケースに特別な処理をすれば、確かに計算コストを削減できます。
確かに、正確な比較を避けることができるように BLAS ルーチンを設計することもできます (例:いくつかのフラグを使用します)。しかし、LAPACK にはそれが不可能な例がたくさんあります。
追記:
確かに、「完全に等しい」かどうかをチェックしたくないケースはたくさんあります。多くの人にとって、これは対処しなければならない唯一のケースかもしれません。私が指摘したいのは、他にもケースがあるということだけです。
LAPACK は Fortran で書かれていますが、数値ソフトウェアに他のプログラミング言語を使用している場合でも、ロジックは同じです。
P6 以前で実行している場合を除き、float ビットを int として解釈しないでください。
たとえメモリを介してベクトルレジスタから整数レジスタにコピーすることになったとしても、またパイプラインが停止したとしても、それが最も堅牢な比較を提供する限り、それは私が出会った最良の方法です。浮動小数点エラーの数。
つまりそれは支払う価値のある価格です。
これは、相対的および絶対的エラー許容度を組み合わせることにより、ほとんどの問題に対処するようです。ULP アプローチの方が優れていますか?もしそうなら、なぜですか?
ULP は、2 つの浮動小数点数間の「距離」を直接測定するものです。これは、相対誤差値と絶対誤差値を計算する必要がなく、それらの値が「ほぼ正確」であることを確認する必要もないことを意味します。ULP を使用すると、数値をどの程度近づけたいかを直接表現でき、同じしきい値は小さい値でも大きな値でも同様に機能します。
浮動小数点エラーがある場合は、これよりもさらに多くの問題が発生します。それは個人の主観によると思いますが。
誤差の蓄積を最小限に抑えるために数値解析を行ったとしても、誤差を取り除くことはできず、(実数で計算している場合は) 同一であるはずなのに、(実数で計算できないため) 異なる結果が残る可能性があります。
2 つの float が等しいことを探している場合、私の意見では、それらはまったく等しいはずです。浮動小数点の丸めの問題に直面している場合は、固定小数点表現の方が問題に適している可能性があります。
2 つの float が等しいことを探している場合、私の意見では、それらはまったく等しいはずです。浮動小数点の丸めの問題に直面している場合は、固定小数点表現の方が問題に適している可能性があります。
おそらく、そのようなアプローチによって生じる範囲やパフォーマンスの損失を許容することはできないでしょう。
@ドクターピザ:私はパフォーマンスの専門家ではありませんが、(ほとんどの場合) 固定小数点演算は浮動小数点演算よりも高速であると予想しています。
@クレイグH:もちろん。それを印刷しても全く問題ありません。a または b がお金を保管する場合は、固定小数点で表す必要があります。私は、そのようなロジックを浮動小数点数に関連付けるべき実際の例を考えるのに苦労しています。フロートに適したもの:
- 重み
- ランク
- 距離
- 現実世界の値 (ADC からのような)
これらすべてについて、人間が解釈できるように数値を入力して単純に結果をユーザーに提示するか、比較ステートメントを作成します (たとえそのようなステートメントが「このものは他のものの 0.001 以内である」であっても)。私のような比較ステートメントは、アルゴリズムのコンテキストでのみ役立ちます。「0.001以内」の部分は何によって決まりますか 物理的な あなたが尋ねている質問。それは私の0.02です。それとも100分の2と言うべきでしょうか?
それはむしろあなたが彼らと何をしているかに依存します。IEEEフロートと同じ範囲の固定点タイプは、何倍も遅くなります(そして多くの倍)。
さて、しかし、無限に小さいビット解像度が必要な場合は、元の点に戻ります。== と != は、このような問題の文脈では意味を持ちません。
int を使用すると、最大 10^9 の値 (範囲に関係なく) を表現できます。これは、それらのうち 2 つが等しいことが重要な状況には十分だと思われます。それでも十分でない場合は、64 ビット OS を使用すると、約 10^19 の個別の値が得られます。
int では (たとえば) 0 から 10^200 の範囲の値を表現できます。影響を受けるのはビット解像度だけです (解像度は 1 より大きくなりますが、繰り返しになりますが、そのような範囲を持つアプリケーションはありません)そのような決意として)。
要約すると、すべての場合において、1 つは値の連続体を表しており、その場合 != と == は無関係であるか、1 つは int (または別の固定値にマッピングできる) 値の固定セットを表しているかのどちらかだと思います。 -精密タイプ)。
INTを使用すると、2つの状況が等しい状況で十分と思われる〜10^9の値(範囲に関係なく)を表現できます。それだけでは不十分な場合は、64ビットOSを使用すると、約10^19の異なる値があります。
実はその限界に達してしまいました…私は、簡単に 10^10 サイクルに達するシミュレーションで、ps 単位の時間とクロック サイクル単位の時間をやりくりしようとしていました。何をしても、すぐに 64 ビット整数のわずかな範囲をオーバーフローしてしまいました...10^19 はあなたが思っているほど大したものではありません。今すぐ 128 ビット コンピューティングをちょうだい!
浮動小数点数を使用すると、値の下端がロット ゼロでオーバーフローするため、数学的問題の解決策を得ることができました。したがって、基本的に、精度を損なうことなく数値内で小数点を浮動小数点で囲むことができます (64 ビット int と比較して、float の仮数部で許可される値の数がより制限されているのは気に入りましたが、どうしてもその範囲が必要でした)。 )。
そして、比較するために物事を整数に戻します。
面倒だったので、最終的にはその試み全体を破棄し、float と < と > だけに頼って作業を完了しました。完璧ではありませんが、想定されているユースケースでは機能します。
2 つの float が等しいことを探している場合、私の意見では、それらはまったく等しいはずです。浮動小数点の丸めの問題に直面している場合は、固定小数点表現の方が問題に適している可能性があります。
おそらく私は問題をもっとよく説明したほうがよいでしょう。C++ では、次のコードになります。
#include <iostream>
using namespace std;
int main()
{
float a = 1.0;
float b = 0.0;
for(int i=0;i<10;++i)
{
b+=0.1;
}
if(a != b)
{
cout << "Something is wrong" << endl;
}
return 1;
}
「何かが間違っています」というフレーズが表示されます。そうすべきだと言っているのですか?
P6 以前で実行している場合を除き、float ビットを int として解釈しないでください。
これは、浮動小数点エラーに直面しても最も堅牢な比較を提供する限り、私がこれを行うための最良の方法です。
浮動小数点エラーがある場合は、これよりもさらに多くの問題が発生します。それは個人の主観によると思いますが。