今日、私はいくつかのC++コード(他の誰かによって書かれた)を見ていて、このセクションを見つけました:
double someValue = ...
if (someValue < std::numeric_limits<double>::epsilon() &&
someValue > -std::numeric_limits<double>::epsilon()) {
someValue = 0.0;
}
これが理にかなっているかどうかを把握しようとしています。
epsilon()
のドキュメントには次のように書かれています:
この関数は、1と、[doubleで]表現可能な1より大きい最小値との差を返します。
これは0にも適用されますか、つまりepsilon()
は0より大きい最小値ですか?または、0
と0 + epsilon
の間に、double
で表すことができる数字がありますか?
そうでない場合、比較はsomeValue == 0.0
と同等ではありませんか?
64ビットIEEE doubleを想定すると、52ビットの仮数と11ビットの指数があります。ビットに分割しましょう:
1.0000 00000000 00000000 00000000 00000000 00000000 00000000 × 2^0 = 1
1より大きい最小の表現可能な数
1.0000 00000000 00000000 00000000 00000000 00000000 00000001 × 2^0 = 1 + 2^-52
したがって:
epsilon = (1 + 2^-52) - 1 = 2^-52
0とイプシロンの間に数字はありますか?たっぷり...表現可能な最小の(通常の)数は次のとおりです。
1.0000 00000000 00000000 00000000 00000000 00000000 00000000 × 2^-1022 = 2^-1022
実際、0とイプシロンの間には(1022 - 52 + 1)×2^52 = 4372995238176751616
の数字があり、これはすべての正の表現可能な数字の47%です...
テストは確かにsomeValue == 0
と同じではありません。浮動小数点数の全体的な考え方は、指数と仮数を格納することです。したがって、これらは、一定数の2進数の有効桁数(IEEE倍精度の場合は53)で値を表します。表現可能な値は、1の近くよりも0の近くではるかに密にパックされています。
より馴染みのある10進数システムを使用するために、指数付きの「4つの有効数字まで」の10進数値を格納するとします。次に、1
より大きい次の表現可能な値は1.001 * 10^0
であり、epsilon
は1.000 * 10^-3
です。ただし、1.000 * 10^-4
も表現可能です。指数に-4を格納できると仮定します。 IEEEのdouble canがepsilon
の指数よりも小さい指数を格納するということは、私のWordで理解できます。
このコードだけでは、epsilon
を具体的にバインドとして使用することが理にかなっているかどうかを判断することはできません。コンテキストを調べる必要があります。 epsilon
は、someValue
を生成した計算のエラーの合理的な推定値である可能性がありますが、そうではない可能性があります。
イプシロンは1と1を超えて表現できる次の最高数との差であり、0と0を超えて表現できる次の最高数との差ではないため、0とイプシロンの間に存在する数があります(ある場合、コードはほとんど何もしません):-
#include <limits>
int main ()
{
struct Doubles
{
double one;
double epsilon;
double half_epsilon;
} values;
values.one = 1.0;
values.epsilon = std::numeric_limits<double>::epsilon();
values.half_epsilon = values.epsilon / 2.0;
}
デバッガーを使用して、メインの最後でプログラムを停止し、結果を見ると、イプシロン/ 2はイプシロン、0、1とは異なることがわかります。
したがって、この関数は+/-イプシロンの間の値を取り、それらをゼロにします。
次のプログラムを使用して、数字(1.0、0.0、...)の周りのイプシロンの近似(可能な限り小さい差)を印刷できます。次の出力を印刷します。epsilon for 0.0 is 4.940656e-324
epsilon for 1.0 is 2.220446e-16
イプシロンは、指数がその数値のサイズに調整できるため、イプシロン値を調べるために使用する数値が小さいほど、イプシロンが小さくなることを少し考えて明らかにします。
#include <stdio.h>
#include <assert.h>
double getEps (double m) {
double approx=1.0;
double lastApprox=0.0;
while (m+approx!=m) {
lastApprox=approx;
approx/=2.0;
}
assert (lastApprox!=0);
return lastApprox;
}
int main () {
printf ("epsilon for 0.0 is %e\n", getEps (0.0));
printf ("epsilon for 1.0 is %e\n", getEps (1.0));
return 0;
}
16ビットのレジスタに収まるおもちゃの浮動小数点数で作業しているとします。符号ビット、5ビットの指数、および10ビットの仮数があります。
この浮動小数点数の値は仮数であり、2進数の10進数値として解釈され、2のべき乗で累乗されます。
約1の指数はゼロに等しくなります。したがって、仮数の最小桁は1024分の1です。
ほぼ1/2の指数はマイナス1であるため、仮数の最小部分は半分になります。 5ビットの指数では、負の16に達する可能性があり、その時点で仮数の最小部分は32mの1部分に相当します。そして、負の16指数では、値は32kの1つの部分の周りにあり、上記で計算した1つの周りのイプシロンよりもゼロにはるかに近くなります!
これはおもちゃの浮動小数点モデルで、実際の浮動小数点システムのすべての癖を反映しているわけではありませんが、イプシロンより小さい値を反映する機能は実際の浮動小数点値とかなり似ています。
X
とX
の次の値の違いは、X
によって異なります。epsilon()
は、1
と1
の次の値の違いのみです。0
と0
の次の値の違いは、epsilon()
ではありません。
代わりに、次のようにstd::nextafter
を使用してdouble値と0
を比較できます。
bool same(double a, double b)
{
return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b
&& std::nextafter(a, std::numeric_limits<double>::max()) >= b;
}
double someValue = ...
if (same (someValue, 0.0)) {
someValue = 0.0;
}
仮数部と指数部のため、これを0に適用することはできません。指数のため、イプシロンより小さい非常に小さな数を格納できますが、(1.0-"非常に小さな数")のようなことを行おうとすると、1.0になります。イプシロンは、値ではなく、仮数である値の精度の指標です。格納できる数値の正しい結果の10進数を示します。
システムが1.000000000000000000000と1.000000000000000000001を区別できないとしましょう。 1.0と1.0 + 1e-20です。 -1e-20と+ 1e-20の間に表現できる値がまだあると思いますか?
IEEE浮動小数点では、最小の非ゼロの正の値と最小の非ゼロの負の値の間に、正のゼロと負のゼロの2つの値があります。値がゼロ以外の最小値の間にあるかどうかをテストすることは、ゼロと等しいかどうかをテストすることと同等です。ただし、負のゼロを正のゼロに変更するため、割り当てには効果があります。
浮動小数点形式には、最小の有限の正の値と負の値の間の3つの値、つまり正の無限小、符号なしゼロ、負の無限小があります。私は実際にそのように動作する浮動小数点形式に精通していませんが、そのような動作は完全に合理的であり、IEEEのそれよりも間違いなく優れています(おそらく、それをサポートするために追加のハードウェアを追加する価値があるほど十分ではありませんが、数学的には1 /(1/INF)、1 /(-1/INF)、および1 /(1-1)は、3つの異なるゼロを示す3つの異なるケースを表す必要があります)。 Cの標準が、符号付きの無限小数が存在する場合、それらをゼロと比較する必要があることを強制するかどうかはわかりません。そうでない場合、上記のようなコードは、たとえば数を2で繰り返し除算すると、「無限小」に留まるのではなく、最終的にゼロになります。
また、このような機能を持つための良いreasonは、「非正規数」(暗黙の先頭の「1」を使用できない非常に小さな数)を削除することです特別なFP表現があります)。なぜこれをしたいのですか?一部のマシン(特に、一部の古いPentium 4)は、非正規化の処理時に非常に遅くなるためです。その他は少し遅くなります。アプリケーションがこれらの非常に小さな数を実際に必要としない場合、それらをゼロにフラッシュすることは良い解決策です。これを検討するのに適した場所は、IIRフィルターまたは減衰関数の最後のステップです。