IEEE float と double が等しいかどうかを比較するための最良の方法は何ですか? いくつかの方法について聞いたことがありますが、コミュニティがどう考えているかを知りたかったのです。
15 に答える
私が考える最良のアプローチは、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;
}
ダブルスにも同様のテクニックを使用できます。トリックは、float を変換して (整数のように) 並べ替えてから、それらがどのように異なるかを確認することです。
このいまいましいものが私のアンダースコアを台無しにしている理由がわかりません。編集: ああ、おそらくそれはプレビューのアーティファクトです。それなら大丈夫です。
私が使用している現在のバージョンはこれです
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アプローチの方が優れていますか? もしそうなら、なぜですか?
@DrPizza:私はパフォーマンスの第一人者ではありませんが、固定小数点演算は浮動小数点演算よりも高速であると期待しています(ほとんどの場合)。
それはむしろあなたが彼らと何をしているかに依存します。IEEEフロートと同じ範囲の固定小数点タイプは、何倍も遅くなります(そして何倍も大きくなります)。
フロートに適したもの:
3Dグラフィックス、物理学/工学、シミュレーション、気候シミュレーション...
数値計算ソフトウェアでは、2 つの浮動小数点数が正確に等しいかどうかをテストしたいことがよくあります。LAPACK には、そのような場合の例がたくさんあります。確かに、最も一般的なケースは、浮動小数点数が「ゼロ」、「1」、「2」、「半分」に等しいかどうかをテストする場合です。誰かが興味を持っている場合は、いくつかのアルゴリズムを選択して、より詳細に説明できます。
また、BLAS では、浮動小数点数が正確に 0 か 1 かを確認したいことがよくあります。たとえば、ルーチン dgemv は次の形式の操作を計算できます。
- y = ベータ*y + アルファ*A*x
- y = ベータ*y + アルファ*A^T*x
- y = ベータ*y + アルファ*A^H*x
したがって、ベータが 1 に等しい場合は「プラス割り当て」があり、ベータがゼロに等しい場合は「単純割り当て」があります。したがって、これらの (一般的な) ケースを特別に処理すれば、計算コストを確実に削減できます。
もちろん、正確な比較を回避できるように BLAS ルーチンを設計することもできます (たとえば、いくつかのフラグを使用)。ただし、LAPACK はそれが不可能な例でいっぱいです。
PS:
確かに「完全に等しい」のチェックをしたくない場合が多いです。多くの人にとって、これは対処しなければならない唯一のケースかもしれません。指摘したいのは、他のケースもあるということだけです。
LAPACK は Fortran で書かれていますが、数値ソフトウェアに他のプログラミング言語を使用している場合でも、ロジックは同じです。
浮動小数点エラーがある場合は、これよりもさらに多くの問題があります。それは個人的な見方次第だと思いますが。
エラーの蓄積を最小限に抑えるために数値解析を行っても、それを排除することはできず、同じであるはずの結果(実数で計算する場合)が異なる(実数で計算できないため)結果が残る可能性があります。
あなたが等しい2つのフロートを探しているなら、それらは私の意見では同じように等しいはずです。浮動小数点の丸めの問題に直面している場合は、おそらく固定小数点表現の方が問題に適しています。
あなたが等しい2つのフロートを探しているなら、それらは私の意見では同じように等しいはずです。浮動小数点の丸めの問題に直面している場合は、おそらく固定小数点表現の方が問題に適しています。
おそらく、そのようなアプローチがもたらす範囲やパフォーマンスの損失を許容することはできません。
あなたが等しい2つのフロートを探しているなら、それらは私の意見では同じように等しいはずです。浮動小数点の丸めの問題に直面している場合は、おそらく固定小数点表現の方が問題に適しています。
おそらく私は問題をよりよく説明する必要があります。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;
}
「何かがおかしい」というフレーズを印刷します。あなたはそれがすべきだと言っていますか?
@DrPizza:私はパフォーマンスの第一人者ではありませんが、固定小数点演算は浮動小数点演算よりも高速であると期待しています(ほとんどの場合)。
@クレイグH:もちろんです。それを印刷しても大丈夫です。aまたはbがお金を保管する場合、それらは固定小数点で表す必要があります。私は、そのようなロジックがフロートに関連付けられるべきである実世界の例を考えるのに苦労しています。フロートに適したもの:
- 重み
- ランク
- 距離
- 実世界の値(ADCなど)
これらすべてのことについて、あなたは数を数え、人間の解釈のために結果をユーザーに提示するか、または比較ステートメントを作成します(そのようなステートメントが「このものはこの他のものの0.001以内である」としても)。私のような比較ステートメントは、アルゴリズムのコンテキストでのみ役立ちます。「0.001以内」の部分は、質問している物理的な質問によって異なります。それは私の0.02です。それとも100分の2と言うべきですか?
intを使用すると、(範囲に関係なく)〜10 ^ 9の値を表現できます。これは、2つが等しいことを気にする状況では十分と思われます。それでも不十分な場合は、64ビットOSを使用すると、約10^19個の個別の値が得られます。
私は実際にその限界に達しました...私は、10 ^ 10サイクルに簡単に達するシミュレーションで、ps単位の時間とクロックサイクル単位の時間を調整しようとしていました。私が何をしたとしても、64ビット整数の小さな範囲をすぐにオーバーフローさせました... 10 ^ 19はあなたが思っているほどではありません、今は128ビットコンピューティングです!
フロートを使用すると、ローエンドで値がロットゼロでオーバーフローしたため、数学的な問題の解決策を得ることができました。したがって、基本的に、精度を損なうことなく、数値に小数点が浮動します(64ビット整数と比較して、浮動小数点の仮数で許可される値の数を制限することができますが、必死に範囲が必要です! )。
そして、物事は比較するために整数に変換されます。
迷惑で、結局、私はすべての試みを破棄し、作業を完了するためにフロートと<および>に依存しました。完璧ではありませんが、想定されるユースケースで機能します。
それはむしろ、あなたが彼らと何をしているかに依存します. IEEE float と同じ範囲の固定小数点型は、何倍も遅くなります (そして何倍も大きくなります)。
わかりましたが、非常に小さなビット解像度が必要な場合は、元のポイントに戻ります: == と != は、そのような問題のコンテキストでは意味がありません。
int を使用すると、(範囲に関係なく) ~10^9 の値を表現できます。これは、2 つの値が等しいことを気にする状況には十分に思えます。それでも十分でない場合は、64 ビット OS を使用すると、約 10^19 個の異なる値が得られます。
0 から 10^200 (たとえば) の範囲の値を int で表現できます。影響を受けるのはビット解像度だけです (解像度は 1 より大きくなりますが、そのような範囲を持つアプリケーションもありませんその種の決議として)。
要約すると、すべての場合で、1 つは値の連続体を表していると思います。この場合、!= と == は無関係です。または、1 つは int (または別の固定値) にマップできる固定値セットを表しています。 -精密タイプ)。
P6 以前で実行している場合を除き、float ビットを int として解釈しないでください。
メモリを介してベクトルレジスタから整数レジスタにコピーする場合でも、パイプラインを失速させる場合でも、直面している場合でも最も堅牢な比較を提供する限り、これが最善の方法です。浮動小数点エラーの。
つまり、それは支払う価値のある価格です。
浮動小数点エラーに直面しても最も堅牢な比較を提供する限り、これは私が遭遇した最良の方法です。
浮動小数点エラーがある場合は、これよりもさらに多くの問題があります。個人の主観によるとは思いますが。
P6 以前で実行している場合を除き、float ビットを int として解釈しないでください。
これは、相対許容誤差と絶対許容誤差を組み合わせることで、ほとんどの問題を解決しているようです。ULPアプローチの方が優れていますか? もしそうなら、なぜですか?
ULP は、2 つの浮動小数点数間の「距離」を直接測定したものです。これは、相対誤差と絶対誤差の値を思い出す必要がなく、それらの値を「ほぼ正しい」値にする必要がないことを意味します。ULP を使用すると、数値をどれだけ近づけたいかを直接表現でき、同じしきい値が小さい値でも大きい値でも同じように機能します。