イプシロンを使用してdoubleと0を比較する


214

今日、私は(他の誰かが書いた)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より大きいですか?またはそこに数字の間である00 + epsilonで表すことができますかdouble

そうでない場合、比較は同等someValue == 0.0ですか?


3
1付近のイプシロンは0付近のイプシロンよりもはるかに高いため、0と0 + epsilon_at_1の間の値が存在する可能性があります。このセクションの作成者は小さなものを使用したいと思いますが、彼は魔法の定数を使用したくなかったので、この本質的に任意の値を使用しました。
enobayram

2
浮動小数点数の比較は困難であり、イプシロンまたはしきい値の使用も推奨されます。参照してください:cs.princeton.edu/introcs/91floatおよびcygnus-software.com/papers/comparingfloats/comparingfloats.htm
Aditya Kumar Pandey

40
最初のリンクは403.99999999
graham.reeds

6
IMO、この場合、の使用numeric_limits<>::epsilonは誤解を招くものであり、無関係です。実際の値と0の差がεを超えない場合は、0と仮定します。εは、マシン依存の値ではなく、問題の仕様に基づいて選択する必要があります。現在のイプシロンは役に立たないのではないかと思います。ほんの少しのFP演算でもそれ以上のエラーを蓄積する可能性があるからです。
Andrey Vihrov

1
+1。イプシロンは可能な限り最小ではありませんが、必要な精度と実行していることがわかっていれば、ほとんどの実用的なエンジニアリングタスクで所定の目的を果たすことができます。
シェプリン

回答:


192

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

実際(1022 - 52 + 1)×2^52 = 4372995238176751616、0とイプシロンの間の数値があります。これは、表現可能なすべての正の数値の47%です...


27
したがって、「正の数の47%」と言えるのは奇妙です:)
configurator

13
@configurator:いや、あなたはそれを言うことはできません(「自然な」有限尺度は存在しません)。しかし、「正の表現可能数の47%」と言えます。
Yakov Galka

1
@ybungalobill分かりません。指数には11ビットがあります。1つの符号ビットと10つの値ビットです。2 ^ -1024ではなく2 ^ -1022が最小の正の数であるのはなぜですか?
Pavlo Dyban

3
@PavloDyban:指数に符号ビットがないためです。これらはオフセットとしてエンコードされます。エンコードされた指数が0 <= e < 2048仮数である場合、仮数は2の累乗ですe - 1023。例えば指数2^0として符号化されるe=10232^1としてe=10242^-1022としてe=1。の値e=0は、非正規および実際のゼロのために予約されています。
Yakov Galka

2
@PavloDyban:通常2^-1022の最小数でもあります。最小数は実際にです。これは非正規であり、仮数部が1より小さいため、指数でエンコードされます。0.0000 00000000 00000000 00000000 00000000 00000000 00000001 × 2^-1022 = 2^-1074e=0
Yakov Galka

17

テストは確かに同じではありませんsomeValue == 0。浮動小数点数の全体的な考え方は、指数と仮数を格納することです。したがって、それらは、特定の数の2進数の有効桁数(IEEE doubleの場合は53)で値を表します。表現可能な値は、1に近い場合よりも0に近い場合の方がはるかに密にパックされます。

より一般的な10進法を使用するために、指数付きの「有効数字4桁まで」の10進値を格納するとします。次に、1is 1.001 * 10^0epsilonis よりも大きい次の表現可能な値です1.000 * 10^-3。ただし1.000 * 10^-4、指数が-4を格納できると仮定すると、これも表現可能です。IEEEのdouble はの指数よりも小さい指数格納できると私の言葉で言うことができepsilonます。

このコードだけではepsilon、特にバインドとして使用するのが理にかなっているかどうかを判断できないため、コンテキストを確認する必要があります。これepsilonは、を生成した計算のエラーの合理的な推定値であるsomeValue可能性がありますが、そうではない可能性があります。


2
良い点ですが、それが事実である場合でも、適切な名前の変数にエラーをバインドし、比較でそれを使用することをお勧めします。現状では、魔法の定数と同じです。
enobayram

多分私は私の質問でより明確であったはずです:イプシロンが計算エラーをカバーするのに十分な「しきい値」であったかどうか、この比較が等しいかどうかは質問しませsomeValue == 0.0んでした。
Sebastian Krysmanski

13

0とイプシロンの間に存在する数値があります。イプシロンは1と1の上で表すことができる次の最大数の差であり、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とは異なることがわかります。

したがって、この関数は+/-イプシロンの間の値を取り、それらをゼロにします。


5

次のプログラムを使用して、数値(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;
}

2
どの実装を確認しましたか?これはGCC 4.7には当てはまりません。
アントン・ゴロフ

3

16ビットのレジスターに収まるおもちゃの浮動小数点数を扱っているとします。符号ビット、5ビットの指数、および10ビットの仮数があります。

この浮動小数点数の値は、2進数の10進数値として解釈される仮数であり、指数の2乗です。

約1の指数はゼロです。したがって、仮数の最小桁は1024の一部です。

指数の1/2近くはマイナス1なので、仮数の最小部分は半分の大きさです。5ビットの指数では、負の16に達する可能性があり、その時点で、仮数の最小部分は32mで1つの部分に相当します。また、負の16指数では、値は32kで約1パーツであり、上記で計算した1のイプシロンよりもゼロにはるかに近いです!

これは、実際の浮動小数点システムのすべての癖を反映しないおもちゃの浮動小数点モデルですが、イプシロンより小さい値を反映する機能は、実際の浮動小数点値とかなり似ています。


3

Xとの次の値Xに応じて変化しますX。との次の値の
epsilon()違いのみ1です1。との次の値
の差は00はありません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;
}

2

それはあなたのコンピュータの精度に依存すると思います。このを見てください。イプシロンがdoubleで表されているが、精度が高い場合、比較は以下と同等ではないことがわかります

someValue == 0.0

とにかく良い質問です!


2

仮数部と指数部のため、これを0に適用することはできません。指数のため、イプシロンよりも小さい数を格納できますが、(1.0-"非常に小さい数")のようなことを行おうとすると、1.0になります。イプシロンは、値ではなく、仮数である値の精度のインジケータです。格納できる数の正しい後続の10進数の数を示します。


2

IEEE浮動小数点では、最小の非ゼロの正の値と最小の非ゼロの負の値の間に、正のゼロと負のゼロの2つの値が存在します。値がゼロ以外の最小値の間にあるかどうかをテストすることは、ゼロと等しいかどうかをテストすることと同じです。ただし、負のゼロを正のゼロに変更するため、割り当てには効果がある場合があります。

浮動小数点形式では、正の有限小値と負の有限値の間に、正の小小、符号なしゼロ、負の小小の3つの値がある可能性があります。私は実際にそのように機能する浮動小数点形式に精通していませんが、そのような動作は完全に合理的であり、間違いなくIEEEの動作よりも優れています(おそらく、それをサポートするハードウェアを追加するだけの価値はありませんが、数学的には1 /(1 / INF)、1 /(-1 / INF)、および1 /(1-1)は、3つの異なるゼロを示す3つの異なるケースを表す必要があります)。存在する場合、ゼロに等しいと比較する必要があるであろうCの標準が署名された無限小を義務付けるかどうかはわかりません。そうでない場合、上記のようなコードは、たとえば


(例では)「1 /(1-1)」はゼロではなく無限ではありませんか?
Sebastian Krysmanski

数量(1-1)、(1 / INF)、(-1 / INF)はすべてゼロを表しますが、正の数をそれぞれで除算すると、理論的には3つの異なる結果が得られるはずです(IEEEの計算では最初の2つは同一と見なされます)。
supercat

1

したがって、システムが1.000000000000000000000と1.000000000000000000001を区別できないとしましょう。それは1.0と1.0 + 1e-20です。-1e-20と+ 1e-20の間で表現できる値がまだあると思いますか?


ゼロを除いて、-1e-20と+ 1e-20の間の値があるとは思いません。しかし、これが真実ではないと私が思うからです。
Sebastian Krysmanski

@SebastianKrysmanski:真実ではありません。0からまでの浮動小数点値がたくさんありepsilonます。それはだから浮動小数点、固定されていない点。
スティーブジェソップ

ゼロとは異なる表現可能な最小値は、指数を表すために割り当てられたビット数によって制限されます。したがって、doubleの指数が11ビットの場合、最小数は1e-1023になります。
cababunga

0

また、このような関数を使用する理由は、「デノーマル」(暗黙の先頭の「1」を使用できなくなり、特別なFP表現を持つことができない非常に小さい数値)を削除するためです。なぜこれをしたいのですか?一部のマシン(特に、一部の古いPentium 4s)は、デノーマルの処理時に本当に遅くなります。その他は少し遅くなるだけです。アプリケーションがこれらの非常に小さい数を本当に必要としない場合、それらをゼロにフラッシュすることは良い解決策です。これを検討するのに適した場所は、IIRフィルターまたは減衰関数の最後のステップです。

参照:0.1fを0に変更するとパフォーマンスが10倍遅くなるのはなぜですか?

およびhttp://en.wikipedia.org/wiki/Denormal_number


1
これにより、単に非正規化された数よりも多くの数が削除されます。Planckの定数または電子の質量をゼロに変更します。これらの数値を使用すると、非常に間違った結果が得られます。
gnasher729 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.