web-dev-qa-db-ja.com

Objective-Cでフロートを比較する奇妙な問題

アルゴリズムのある時点で、クラスのプロパティのfloat値をfloatと比較する必要があります。だから私はこれを行います:

if (self.scroller.currentValue <= 0.1) {
}

ここで、currentValueはfloatプロパティです。

しかし、私が平等でself.scroller.currentValue = 0.1の場合、ifステートメントは実行されず、コードは実行されません。 0.1をfloatにキャストすることでこれを修正できることがわかりました。このような:

if (self.scroller.currentValue <= (float)0.1) {
}

これは正常に機能します。

なぜこれが起こっているのか誰かが私に説明できますか? 0.1はデフォルトでdoubleとして定義されていますか?

ありがとう。

23
Dimitris

floatdoubleと比較するとき、floatは比較する前にdoubleにキャストされると私は信じています。修飾子のない浮動小数点数は、Cではdoubleと見なされます。

ただし、Cでは、floatとdoubleで0.1を正確に表すことはできません。ここで、floatを使用すると、小さなエラーが発生します。 doubleを使用すると、エラーがさらに小さくなります。ここでの問題は、floatdoubleにキャストすることにより、floatの大きい方のエラーを引き継ぐことです。もちろん、それらは今では同等に比較されていません。

(float)0.1を使用する代わりに、0.1fを使用することもできます。これは少し読みやすくなっています。

30
Georg Schölly

問題は、質問で示唆したように、フロートとダブルを比較していることです。

浮動小数点数の比較には、より一般的な問題があります。これは、浮動小数点数で計算を行うと、計算結果が期待どおりにならない場合があるためです。結果のfloatの最後のビットが間違っていることはかなり一般的です(ただし、不正確さは最後のビットよりも大きくなる可能性があります)。 ==を使用して2つのフロートを比較する場合、フロートを等しくするには、すべてのビットが同じである必要があります。あなたの計算がわずかに不正確な結果を与えるならば、あなたがそれらを期待するとき、それらは等しく比較されません。このように値を比較する代わりに、それらを比較して、ほぼ等しいかどうかを確認できます。これを行うには、フロート間の正の差を取り、それが特定の値(イプシロンと呼ばれる)よりも小さいかどうかを確認します。

適切なイプシロンを選択するには、浮動小数点数について少し理解する必要があります。浮動小数点数は、指定された有効数字の数を表すのと同じように機能します。 5つの有効数字を処理し、計算結果の最後の桁が間違っている場合、1.2345のエラーは+ -0.0001になりますが、1234500のエラーは+ -100になります。常に値1.2345に基づいて許容誤差を計算する場合、比較ルーチンは、10より大きいすべての値(10進数を使用する場合)の==と同じになります。これはバイナリではさらに悪く、すべて2より大きい値です。これは、選択するイプシロンが、比較しているフロートのサイズに関連している必要があることを意味します。

FLT_EPSILONは、1と次に近いフロートの間のギャップです。つまり、数値が1〜2の場合に選択するのが適切なイプシロンですが、値が2より大きい場合、2と次に近いフロートの間のギャップがイプシロンよりも大きいため、このイプシロンを使用しても意味がありません。したがって、フロートのサイズに関連するイプシロンを選択する必要があります(計算の誤差はフロートのサイズに関連するため)。

良い(っぽい)浮動小数点比較ルーチンは次のようになります。

bool compareNearlyEqual (float a, float b, unsigned epsilonMultiplier)       
{
  float epsilon;
  /* May as well do the easy check first. */
  if (a == b)
    return true;

  if (a > b) {
    epsilon = scalbnf(1.0f, ilogb(a)) * FLT_EPSILON * epsilonMultiplier;
  } else {
    epsilon = scalbnf(1.0, ilogb(b)) * FLT_EPSILON * epsilonMultiplier;
  }

  return fabs (a - b) <= epsilon;
}

この比較ルーチンは、渡された最大のフロートのサイズと比較してフロートを比較します。scalbnf(1.0f, ilogb(a)) * FLT_EPSILONは、aと次に近いフロートの間のギャップを見つけます。次に、これにepsilonMultiplierを掛けて、計算結果がどれほど不正確になる可能性があるかに応じて、差のサイズを調整できます。

次のような単純なcompareLessThanルーチンを作成できます。

bool compareLessThan (float a, float b, unsigned epsilonMultiplier)
{
  if (compareNearlyEqual (a, b, epsilonMultiplier)
    return false;

  return a < b;
}

非常によく似たcompareGreaterThan関数を作成することもできます。

このようなフロートを比較することは、必ずしもあなたが望むものではないかもしれないことに注意する価値があります。たとえば、0でない限り、フロートが0に近いことを検出することはありません。これを修正するには、ゼロに近いと思われる値を決定し、これに対する追加のテストを作成する必要があります。

時々、あなたが得る不正確さは、計算結果のサイズに依存しないでしょうが、あなたが計算に入れる値に依存するでしょう。たとえば、sin(1.0f + (float)(200 * M_PI))の結果はsin(1.0f)よりもはるかに正確ではありません(結果は同じである必要があります)。この場合、比較ルーチンは、計算に入力した数値を調べて、回答の許容誤差を知る必要があります。

6
James Snook

Cでは、0.1のような浮動小数点リテラルはdoubleであり、floatではありません。比較するデータ項目の型が異なるため、より正確な型(double)で比較します。私が知っているすべての実装では、floatの表現はdoubleよりも短くなっています(通常、小数点以下6桁と14桁のように表されます)。さらに、算術演算は2進数で行われ、1/10は2進数で正確に表現されていません。

したがって、精度を失うフロート0.1を取得し、それを2倍に拡張し、精度が低下する2倍の0.1と比較することを期待しています。

これを10進数で実行し、floatが3桁、doubleが6桁で、1/3と比較しているとします。

保存されているfloat値は0.333です。値が0.333333のdoubleと比較しています。フロート0.333をダブル0.333000に変換すると、異なることがわかります。

4
David Thornley

0.1は、実際にはバイナリを格納するのが非常に難しい値です。基数2では、1/10は無限に繰り返される分数です

0.0001100110011001100110011001100110011001100110011...

いくつかが指摘しているように、比較はまったく同じ精度の定数で行う必要があります。

4
epatel

ダブルとフロートは、バイナリの仮数ストアの値が異なります(フロートは23ビット、ダブル54)。これらが等しくなることはほとんどありません。

ウィキペディアのIEEE Float Pointの記事 この違いを理解するのに役立つかもしれません。

4
MarkPowell

一般に、どの言語でも、floatのような型の同等性を実際に期待することはできません。あなたの場合、あなたはより多くのコントロールを持っているように見えるので、0.1はデフォルトではフロートではないように見えます。 sizeof(0.1)(vs。sizeof(self.scroller.currentValue)でそれを見つけることができるでしょう。

1
Lou Franco