web-dev-qa-db-ja.com

浮動小数点値を比較することはどれほど危険ですか?

解像度に依存しない座標系のため、UIKitCGFloatを使用しています。

しかし、たとえばframe.Origin.x0であるかどうかを確認するたびに、気分が悪くなります。

if (theView.frame.Origin.x == 0) {
    // do important operation
}

==<=>=<>と比較した場合、CGFloatは誤検知に対して脆弱ではありませんか?いいえ。これは浮動小数点であり、それらは不正確な問題を抱えています。例えば0.0000000000041

Objective-Cは比較時にこれを内部的に処理していますか、それともゼロとして読み込まれるOrigin.xがtrueとして0と比較されないということが起こり得ますか?

378
Proud Member

まず、浮動小数点値の動作は「ランダム」ではありません。正確な比較は、多くの実際の使用法で意味があり、実際に有効です。しかし、浮動小数点を使用する場合は、浮動小数点の仕組みを認識する必要があります。浮動小数点が実数のように機能すると仮定することを誤ると、コードがすぐに壊れてしまいます。浮動小数点の結果に大きなランダムファズが関連付けられていると仮定すると(ここでの回答のほとんどが示唆するように)、最初は動作しているように見えますが、大きな誤差と破損したコーナーケースが発生します。

まず、浮動小数点を使用してプログラミングする場合は、次をお読みください。

すべてのコンピューター科学者が浮動小数点演算について知っておくべきこと

はい、すべて読んでください。負担が大きすぎる場合は、読む時間があるまで、計算に整数/固定小数点を使用する必要があります。 :-)

とはいえ、正確な浮動小数点比較の最大の問題は次のとおりです。

  1. 多くの値をソースに書き込んだり、scanfまたはstrtodを使用して読み込んだり、が浮動小数点値として存在しないという事実静かに最も近い近似に変換されます。これがdemon9733の答えが言っていたことです。

  2. 実際の結果を表すのに十分な精度がないために、多くの結果が丸められるという事実。これを見ることができる簡単な例は、x = 0x1fffffey = 1をフロートとして追加することです。ここでは、xの仮数部の精度は24ビット(ok)であり、yの精度は1ビットのみですが、それらを追加すると、それらのビットは重複する場所になく、結果には25ビットが必要になります精度の。代わりに、丸められます(デフォルトの丸めモードで0x2000000に)。

  3. 正しい値のために無限に多くの場所を必要とするため、多くの結果が丸められるという事実。これには、1/3(無限に多くの場所をとる10進数からおなじみです)などの合理的な結果と、1/10(5は2のべき乗ではないため、バイナリでも無限に多くの場所をとります)の両方が含まれます。完全な正方形ではないものの平方根などの不合理な結果も同様です。

  4. 二重丸め。一部のシステム(特にx86)では、浮動小数点式はそれらの名義型よりも高い精度で評価されます。つまり、上記のタイプの丸めのいずれかが発生すると、2つの丸めステップが行われ、最初に結果がより精度の高いタイプに丸められ、次に最終タイプに丸められます。例として、1.49を整数に丸めた場合(1)に10進数で何が起こるか、最初に小数点以下1桁に丸めた場合(1.5)、次にその結果を整数に丸めた場合(2)を考えます。コンパイラーの動作(特にバグの多いGCCのような非準拠コンパイラー)の動作は予測できないため、これは実際には浮動小数点で扱うのが最も厄介な領域の1つです。

  5. 超越関数(trigexplogなど)は、正しく丸められた結果が得られるように指定されていません。結果は、精度の最後の1単位内で正しいと指定されただけです(通常、1ulpと呼ばれます)。

浮動小数点コードを記述するときは、結果が不正確になる可能性のある数値で何をしているのかを念頭に置いて、それに応じて比較を行う必要があります。多くの場合、「イプシロン」と比較するのは理にかなっていますが、イプシロンは、絶対定数ではなく、比較する数値のの大きさに基づいている必要があります。 (絶対定数のイプシロンが機能する場合、それは浮動小数点ではなく固定小数点が仕事に最適なツールであることを強く示しています!)

Edit:特に、マグニチュード相対イプシロンチェックは次のようになります。

if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))

ここで、FLT_EPSILONfloat.hからの定数(DBL_EPSILON fordoublesまたはLDBL_EPSILONsのlong doubleに置き換え)とKはa計算の累積誤差が最後の場所のK単位によって明確に制限されるように選択する定数(および、誤差範囲の計算が正しいかどうかわからない場合は、Kを数回作成するあなたの計算がそうあるべきだと言うものよりも大きい)。

最後に、これを使用する場合、FLT_EPSILONは非正規化には意味がないため、ゼロ付近で特別な注意が必要になる場合があることに注意してください。簡単な修正方法は次のとおりです。

if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y) || fabs(x-y) < FLT_MIN)

同様に、doubleを使用する場合はDBL_MINに置き換えます。

452
R..

0はIEEE754浮動小数点数として(あるいは私が今まで扱ったことのあるf-p数の他の実装を使って)正確に表現できるので、0との比較はおそらく安全です。しかし、あなたのプログラムが(theView.frame.Origin.xのように)あなたが信じなければならない値が0であるべきだがあなたの計算が0であることを保証することができない値を計算するならば、噛み付くかもしれません。

少しわかりやすくすると、次のような計算です。

areal = 0.0

(あなたの言語やシステムが壊れていない限り)(areal == 0.0)がtrueを返すような値を作成しますが、

areal = 1.386 - 2.1*(0.66)

ではないかもしれない。

あなたの計算が0である値を生成することをあなた自身が確信できるのであれば(そしてそれらが0であるべき値を生成するのではなく)、あなたは先に進み、0とfp値を比較することができます。 、「寛容平等」の通常のアプローチに固執するのが一番です。

最悪の場合、f-p値の不注意な比較は非常に危険になる可能性があります。アビオニクス、武器誘導、発電所操作、車両ナビゲーション、計算が現実世界を満たすほとんどすべてのアプリケーションを考えてください。

怒っている鳥にとっては、それほど危険ではありません。

34

私は他とは少し違う答えをしたいと思います。述べたようにあなたの質問に答えるのにそれらは素晴らしいですが、あなたが知る必要があるものやあなたの本当の問題が何であるかのためにおそらくそうではありません。

グラフィックの浮動小数点数は問題ありません。しかしフロートを直接比較する必要はほとんどありません。なぜあなたはそれをする必要がありますか?グラフィックは間隔を定義するために浮動小数点数を使用します。フロートがフロートで定義された間隔内にあるかどうかを比較することは常に明確に定義されており、一貫性があるだけで、正確でも正確でもありません。ピクセル(これもまたインターバルです!)を割り当てることができる限り、それはすべてのグラフィックの必要性です。

ですから、あなたのポイントが[0..width [の範囲外であるかどうかをテストしたいのであれば、これで問題ありません。包含を一貫して定義するようにしてください。例えば、常にinside(x> = 0 && x <width)を定義します。交差テストやヒットテストについても同じことが言えます。

ただし、ウィンドウがドッキングされているかどうかなど、グラフィック座標を何らかのフラグとして使用している場合は、これを実行しないでください。代わりにグラフィックスプレゼンテーション層とは別のブールフラグを使用してください。

20
starmole

ゼロとの比較上記の答えで述べたように)ゼロが計算値でない限り、(can安全な操作になります。これは、ゼロは浮動小数点数で完全に表現可能な数であるためです。

完全に表現可能な値を話すと、2のべき乗の概念で24ビットの範囲が得られます(単精度)。そのため、1、2、4は、.5、.25、および.125と同様に、完全に表現可能です。あなたのすべての重要なビットが24ビットである限り、あなたは金色です。したがって10.625は正確に表現できます。

これは素晴らしいことですが、圧力がかかるとすぐに崩壊します。 2つのシナリオが思い浮かびます。1)計算が関係しているとき。そのsqrt(3)* sqrt(3)== 3を信頼しないでください。他の回答のいくつかが示唆するように、それはおそらくイプシロン以内にはないでしょう。 2)2のべき乗でない(NPOT)が関与しているとき。奇妙に聞こえるかもしれませんが、0.1は2進数で無限級数なので、このような数値を含む計算は最初から不正確になります。

(ああ、そして元の質問では、ゼロとの比較について述べていました。-0.0も完全に有効な浮動小数点値であることを忘れないでください。)

13
JHumphrey

['正しい答え'はKを選択することについての説明です。 Kを選択することは、VISIBLE_SHIFTを選択するのと同じくらい特別なことになりますが、Kを選択することは、VISIBLE_SHIFTとは異なり、どの表示プロパティにも基づいていないため、あまり明白ではありません。それであなたの毒を選んでください - Kを選ぶかVISIBLE_SHIFTを選んでください。この答えはVISIBLE_SHIFTを選択することを支持し、それからKを選択することの難しさを示しています。]

正確にはラウンドエラーがあるため、論理演算には「正確な」値の比較を使用しないでください。視覚ディスプレイ上の位置の特定のケースでは、位置が0.0または0.0000000003のどちらであっても問題にならないかもしれません - 違いは目に見えません。だからあなたの論理は次のようになるはずです。

#define VISIBLE_SHIFT    0.0001        // for example
if (fabs(theView.frame.Origin.x) < VISIBLE_SHIFT) { /* ... */ }

しかし、結局のところ、「目に見えない」はあなたのディスプレイのプロパティに依存します。あなたがディスプレイの上限を設定できるのであれば(できるはずです)。次に、VISIBLE_SHIFTを選択して、その上限の小部分にします。

さて、 '正しい答え'はKにかかっているので、Kを選んで調べてみましょう。上記の「正しい答え」は次のように述べています。

Kは、計算の累積誤差が最後の場所でK単位によって確実に制限されるように選択した定数です(そして誤差範囲の計算が正しく行われたかどうかわからない場合は、Kを計算の数倍大きくします)。それがあるべきだと言う)

それでKが必要です。 Kを取得することが、私のVISIBLE_SHIFTを選択するよりも困難で直感的でない場合は、何がうまくいくかを判断します。 Kを見つけるために、たくさんのKの値を調べて動作を確認できるテストプログラムを作成します。 '正しい答え'が使える場合は、Kの選び方が自明です。いいえ?

「正しい答え」の詳細として使用します。

if (fabs(x-y) < K * DBL_EPSILON * fabs(x+y) || fabs(x-y) < DBL_MIN)

Kのすべての値を試してみましょう。

#include <math.h>
#include <float.h>
#include <stdio.h>

void main (void)
{
  double x = 1e-13;
  double y = 0.0;

  double K = 1e22;
  int i = 0;

  for (; i < 32; i++, K = K/10.0)
    {
      printf ("K:%40.16lf -> ", K);

      if (fabs(x-y) < K * DBL_EPSILON * fabs(x+y) || fabs(x-y) < DBL_MIN)
        printf ("YES\n");
      else
        printf ("NO\n");
    }
}
ebg@ebg$ gcc -o test test.c
ebg@ebg$ ./test
K:10000000000000000000000.0000000000000000 -> YES
K: 1000000000000000000000.0000000000000000 -> YES
K:  100000000000000000000.0000000000000000 -> YES
K:   10000000000000000000.0000000000000000 -> YES
K:    1000000000000000000.0000000000000000 -> YES
K:     100000000000000000.0000000000000000 -> YES
K:      10000000000000000.0000000000000000 -> YES
K:       1000000000000000.0000000000000000 -> NO
K:        100000000000000.0000000000000000 -> NO
K:         10000000000000.0000000000000000 -> NO
K:          1000000000000.0000000000000000 -> NO
K:           100000000000.0000000000000000 -> NO
K:            10000000000.0000000000000000 -> NO
K:             1000000000.0000000000000000 -> NO
K:              100000000.0000000000000000 -> NO
K:               10000000.0000000000000000 -> NO
K:                1000000.0000000000000000 -> NO
K:                 100000.0000000000000000 -> NO
K:                  10000.0000000000000000 -> NO
K:                   1000.0000000000000000 -> NO
K:                    100.0000000000000000 -> NO
K:                     10.0000000000000000 -> NO
K:                      1.0000000000000000 -> NO
K:                      0.1000000000000000 -> NO
K:                      0.0100000000000000 -> NO
K:                      0.0010000000000000 -> NO
K:                      0.0001000000000000 -> NO
K:                      0.0000100000000000 -> NO
K:                      0.0000010000000000 -> NO
K:                      0.0000001000000000 -> NO
K:                      0.0000000100000000 -> NO
K:                      0.0000000010000000 -> NO

1e-13を '0'にしたい場合は、Kは1e16以上でなければなりません。

だから、私はあなたが2つの選択肢があると思います。

  1. 私が提案したように、 'epsilon'の値に対してあなたの工学的判断を使って簡単なイプシロン計算をしてください。あなたがグラフィックをしていて、 'zero'があなたの視覚的な資産(イメージなど)を調べて、そしてどんなイプシロンであることができるかを判断することより '目に見える変化'であることを意味します。
  2. カーゴカルトではない答えのリファレンスを読み(そしてその過程で博士号を取得し)、直感的ではない判断を使用してKを選択するまでは、浮動小数点演算を試みないでください。
10
GoZoner

正しい質問:Cocoa Touchのポイントをどう比較するのですか。

正しい答えはCGPointEqualToPoint()です。

別の質問:2つの計算値は同じですか?

ここに投稿された答えは:そうではありません。

それらが近いかどうかを確認する方法?近いかどうかを確認したい場合は、CGPointEqualToPoint()を使用しないでください。しかし、それらが近いかどうかを確認しないでください。点が線の外側にあるかどうか、または点が球の内側にあるかどうかを確認するなど、現実の世界で意味のあることをします。

5
Michael

前回C標準をチェックしたときには、倍精度の浮動小数点演算(合計64ビット、仮数53ビット)がそれ以上の精度である必要はありませんでした。ただし、ハードウェアによっては、より高精度のレジスタで演算を実行する場合があり、この要件は、(レジスタにロードされる数値の精度を超えて)下位ビットをクリアする要件がないことを意味すると解釈されました。それで、最後に眠った人からレジスタに残されたものに依存して、あなたはこのような比較の予想外の結果を得ることができました。

そうは言っても、それを見たときはいつでもそれを消そうと努力しているにもかかわらず、私の仕事場にはgccを使ってコンパイルされlinux上で実行されるCコードがたくさんあります。 。これが、gccが下位ビットをクリアしているのか、80ビットレジスタが現代のコンピュータでのこれらの操作に使用されていないのか、規格が変更されたのか、それとも何のためなのかわかりません。誰かが章や節を引用できるかどうかを知りたいのですが。

4
Lucas Membrane

このようなコードをfloatと0の比較に使用できます。

if ((int)(theView.frame.Origin.x * 100) == 0) {
    // do important operation
}

これは0.1の精度と比較され、この場合はCGFloatには十分です。

1
Igor
-(BOOL)isFloatEqual:(CGFloat)firstValue secondValue:(CGFloat)secondValue{

BOOL isEqual = NO;

NSNumber *firstValueNumber = [NSNumber numberWithDouble:firstValue];
NSNumber *secondValueNumber = [NSNumber numberWithDouble:secondValue];

isEqual = [firstValueNumber isEqualToNumber:secondValueNumber];

return isEqual;

}

0
Abbas Mulani