413

解像度に依存しない座標系のため、UIKit用途を知っています。CGFloat

frame.origin.xしかし、たとえば気分が0悪くなるかどうかを確認したいたびに:

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

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

Objective-C比較するときにこれを内部的に処理していますか、それともゼロとして読み取られる a が true と比較されない可能性がありorigin.xます0か?

4

11 に答える 11

499

まず第一に、浮動小数点値の動作は「ランダム」ではありません。正確な比較は、現実世界の多くの用途で意味をなす可能性があり、実際に意味があります。ただし、浮動小数点を使用する場合は、その動作を認識する必要があります。浮動小数点が実数のように機能すると仮定すると、すぐに壊れるコードが得られます。浮動小数点の結果に大きなランダムファズが関連付けられていると仮定する側で誤りを犯すと(ここでのほとんどの回答が示唆するように)、最初は機能しているように見えますが、最終的には大きなエラーと壊れたコーナーケースが発生するコードが得られます。

まず第一に、浮動小数点でプログラミングしたい場合は、これを読むべきです:

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

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

さて、そうは言っても、正確な浮動小数点比較の最大の問題は次のようになります。

  1. scanfソースに書き込んだり、またはstrtodで読み取ったりする可能性のある多くの値は、浮動小数点値として存在せず、最も近い近似値に静かに変換されるという事実。これは、demon9733 の回答が話していたことです。

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

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

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

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

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

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

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

FLT_EPSILON定数はどこからのもので ( for s またはfor sfloat.hに置き換えてください)、計算の累積誤差が最後の桁の単位によって確実に制限されるように選択した定数です(エラーが発生したかどうかわからない場合)バインドされた計算が正しく、あなたの計算がそうあるべきだと言っているよりも数倍大きくしてください)。DBL_EPSILONdoubleLDBL_EPSILONlong doubleKKK

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

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

DBL_MINdouble を使用する場合も同様に置き換えます。

于 2012-04-26T14:33:33.470 に答える
41

0 は IEEE754 浮動小数点数として正確に表現できるため (または、これまでに使用した fp 数値の他の実装を使用して)、0 との比較はおそらく安全です。ただし、プログラムが値 ( などtheView.frame.origin.x) を計算すると、その値が 0 であるべきだと信じる理由があっても、計算では 0 であるとは保証できない場合、噛まれる可能性があります。

少し明確にするために、次のような計算:

areal = 0.0

(言語またはシステムが壊れていない限り)(area == 0.0)がtrueを返すような値を作成しますが、次のような別の計算

areal = 1.386 - 2.1*(0.66)

ではないかもしれない。

計算が 0 の値を生成することを保証できる場合 (0 であるべき値を生成するだけでなく)、先に進んで fp 値を 0 と比較できます。必要な程度まで保証できない場合、「寛容な平等」の通常のアプローチに固執するのが最善です。

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

Angry Birds にとっては、それほど危険ではありません。

于 2012-04-26T13:55:23.000 に答える
24

他の方とは少し違う回答をしたいと思います。前述のように質問に答えるには最適ですが、知っておくべきことや実際の問題についてはおそらくそうではありません。

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

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

ただし、たとえばウィンドウがドッキングされているかどうかを確認するなど、グラフィック座標を何らかのフラグとして悪用している場合は、これを行うべきではありません。代わりに、グラフィックス プレゼンテーション レイヤーとは別のブール フラグを使用してください。

于 2012-05-14T04:19:50.783 に答える
15

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

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

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

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

于 2012-05-14T03:48:20.597 に答える
12

[「正しい答え」は、を選択することを註解し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を「ゼロ」にしたい場合は、Kを1e16以上にする必要があります。

したがって、2つのオプションがあると思います。

  1. 私が提案したように、「イプシロン」の値について工学的判断を使用して、単純なイプシロン計算を実行します。グラフィックスを実行していて、「ゼロ」が「目に見える変化」であることを意味する場合は、視覚的な資産(画像など)を調べて、イプシロンが何であるかを判断します。
  2. カーゴカルトではない回答のリファレンスを読み(そしてその過程で博士号を取得するまで)浮動小数点の計算を試みないでください。その後、直感的でない判断を使用してを選択しますK
于 2012-04-26T13:51:15.253 に答える
6

正しい質問: Cocoa Touch でポイントを比較するにはどうすればよいですか?

正解:CGPointEqualToPoint()。

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

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

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

于 2013-05-07T12:09:19.483 に答える
4

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

そうは言っても、私がそれを目にするたびに削除するように努力したにもかかわらず、私が働いている組織には、gcc を使用してコンパイルされ、Linux で実行される多くの C コードがあり、非常に長い間、これらの予期しない結果に気付いていません。 . これは、gcc が下位ビットをクリアしているためなのか、最新のコンピューターではこれらの操作に 80 ビット レジスタが使用されていないのか、標準が変更されたのか、それとも何なのかわかりません。誰かが章と節を引用できるかどうか知りたい.

于 2012-05-14T01:41:29.187 に答える
0

このようなコードを使用して、float をゼロと比較できます。

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

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

于 2017-01-22T18:43:13.443 に答える
-1

次の比較関数を使用して、小数点以下の桁数を比較しています。

bool compare(const double value1, const double value2, const int precision)
{
    int64_t magnitude = static_cast<int64_t>(std::pow(10, precision));
    int64_t intValue1 = static_cast<int64_t>(value1 * magnitude);
    int64_t intValue2 = static_cast<int64_t>(value2 * magnitude);
    return intValue1 == intValue2;
}

// Compare 9 decimal places:
if (compare(theView.frame.origin.x, 0, 9)) {
    // do important operation
}
于 2020-03-23T10:17:00.530 に答える
-6

正しいことは、各数値をオブジェクトとして宣言し、そのオブジェクトで 3 つのことを定義することです。1) 等値演算子。2) setAcceptableDifference メソッド。3) 値そのもの。等値演算子は、2 つの値の絶対差が許容値よりも小さい場合に true を返します。

問題に合わせてオブジェクトをサブクラス化できます。たとえば、1 ~ 2 インチの金属の丸棒は、直径の差が 0.0001 インチ未満の場合、直径が等しいと見なされる場合があります。したがって、setAcceptableDifference をパラメーター 0.0001 で呼び出してから、等値演算子を自信を持って使用します。

于 2012-05-02T22:52:19.720 に答える