1. ホーム
  2. オブジェクティブC

[解決済み】浮動小数点値の比較はどのくらい危険か?

2022-03-24 01:20:51

質問

知っている UIKit 用途 CGFloat というのは、解像度に依存しない座標系だからです。

しかし、毎回、例えば frame.origin.x0 気持ち悪くなる。

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

はありません。 CGFloat と比較した場合、誤検出の可能性があります。 == , <= , >= , < , > ? 浮動小数点で、精度不足の問題があるそうです。 0.0000000000041 例えば

Objective-C は比較時にこれを内部的に処理するか、あるいは origin.x とは比較されません。 0 を真とするか?

解決方法は?

まず第一に、浮動小数点値はその動作において"random"ではないことです。厳密な比較は、現実の世界の多くの用途で意味を持ちます。しかし、浮動小数点を使用するのであれば、それがどのように動作するのかを知っておく必要があります。浮動小数点が実数のように動くと思い込んでいると、すぐにコードが壊れてしまいます。浮動小数点の結果が大きなランダムファズを持っていると仮定すると(ここの回答のほとんどが示唆するように)、最初は動作しているように見えても、最終的には大きなエラーや壊れたコーナーケースを持つことになるコードを取得することになります。

まず、浮動小数点を使ったプログラミングをしたい人は、これを読んでおくといいでしょう。

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

はい、全部読んでください。それが負担なら、読む時間ができるまで、計算には整数・固定小数点を使うべきでしょう :-)

さて、そうは言っても、正確な浮動小数点数の比較の最大の問題点は、次のようなことに尽きると思います。

  1. ソースに記述した値、あるいは scanf または strtod , 存在しない を浮動小数点値として使用すると、無言で最も近い近似値に変換されます。demon9733さんの回答はこのことを指しています。

  2. 実際の結果を表すのに十分な精度がないため、多くの結果が丸め込まれてしまうことです。これを見ることができる簡単な例として x = 0x1fffffey = 1 をフロートとして使用します。ここで x は仮数で24ビットの精度を持ち(ok),かつ y は1ビットだけですが、足し算をするとそのビットは重なる場所になく、結果には25ビットの精度が必要になります。その代わり、丸められ ( 0x2000000 で、デフォルトの丸めモードで)。

  3. 正しい値を得るためには無限に多くの場所が必要なため、多くの結果が丸められてるという事実です。これには、1/3 のような有理数(10 進法では無限大の桁数を必要とします)と 1/10 (2 進法では 5 は 2 のべき乗ではないのでこれも無限大の桁数を必要とします)の両方と、完全平方でないものの平方根のような非理数の結果が含まれます。

  4. 二重丸め。一部のシステム(特に x86)では、浮動小数点式はその公称型よりも高精度で評価されます。つまり、上記のような丸めが行われる場合、2回の丸めが行われることになります。最初に高い精度の型に丸められ、次に最終的な型に丸められます。例えば、10進数で1.49を整数に丸めた場合(1)と、まず小数点以下1桁に丸め(1.5)、その結果を整数に丸めた場合(2)とを考えてみましょう。コンパイラ(特にGCCのようなバギーで不適合なコンパイラ)の動作が予測できないからです。

  5. 超越関数 ( trig , exp , log など)は,結果が正しく丸められるように指定されているわけではありません。結果は,精度の最後の場所で1単位以内で正しくなるように指定されているだけです(通常,このような場合は 1ulp ).

浮動小数点コードを書くときは、結果が不正確になる可能性のある数値の扱いに留意し、それに応じて比較を行う必要があります。多くの場合、quot;epsilon"で比較することは理にかなっていますが、そのepsilonは、quot;epsilon"の値に基づいている必要があります。 比較する数値の大きさ 絶対定数ではありません。(絶対定数のイプシロンが有効な場合、浮動小数点ではなく固定小数点がその仕事に適したツールであることを強く示唆します!)

編集する。 特に、マグニチュード相対イプシロン検査は、以下のようになります。

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

ここで FLT_EPSILON の定数です。 float.h (で置き換える)。 DBL_EPSILON に対して double または LDBL_EPSILON に対して long double s)と K は、計算の累積誤差が確実に以下の範囲に収まるような定数です。 K の単位で計算します(誤差の境界の計算が正しいかどうかわからない場合は K を、計算結果の数倍大きくしてください)。

最後に、これを使用する場合、ゼロ付近では特別な注意が必要であることに注意してください。 FLT_EPSILON はデノーマルに対して意味をなさない。手っ取り早いのは、それを作ることでしょう。

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

で、同様に DBL_MIN をダブルスで使用する場合。