web-dev-qa-db-ja.com

==を使用してJavaのフロートを比較することの何が問題になっていますか?

このJava.Sunページ==によると、Javaの浮動小数点数の等価比較演算子です。

ただし、このコードを入力すると:

if(sectionID == currentSectionID)

エディターに入力して静的分析を実行すると、「==と比較したJava0078浮動小数点値」が得られます。

==を使用して浮動小数点値を比較することの何が問題になっていますか?それを行う正しい方法は何ですか?

167
user128807

「等価」のフロートをテストする正しい方法は次のとおりです。

if(Math.abs(sectionID - currentSectionID) < epsilon)

ここで、イプシロンは、希望する精度に応じて、0.00000001などの非常に小さな数値です。

205
Victor

浮動小数点値は少しずれている場合があるため、正確に等しいと報告されない場合があります。たとえば、フロートを「6.1」に設定してから再度印刷すると、「6.099999904632568359375」などの値が報告される場合があります。これは、フロートの動作方法の基本です。したがって、等式を使用してそれらを比較するのではなく、範囲内で比較する必要があります。つまり、フロートと比較する数値の差分が特定の絶対値より小さい場合です。

This Registerの記事は、なぜそうなのかについての良い概要を示しています。有用で興味深い読書。

53
Paul Sonier

誰もが言っていることの背後にある理由を与えるためだけに。

浮動小数点数のバイナリ表現は一種の迷惑です。

バイナリでは、ほとんどのプログラマーは1b = 1d、10b = 2d、100b = 4d、1000b = 8dの相関関係を知っています

まあそれは他の方法でも動作します。

.1b = .5d、.01b = .25d、.001b = .125、...

問題は、.1、.2、.3など、ほとんどの10進数を表す正確な方法がないことです。できることは、バイナリで近似することだけです。システムは、.10000000000001または.999999999999の代わりに.1を表示するように、数字を印刷するときに少しファッジ丸めを行います(おそらく、.1と同様に保存された表現に近い)。

コメントから編集:これが問題である理由は私たちの期待です。 .7または.67または.666667のいずれかに10進数に変換すると、ある時点で2/3がファッジされることを完全に予想します。しかし、.3が2/3と同じように丸められることを自動的に期待しません。 -そしてそれはまさに起こっていることです。

ちなみに、好奇心if盛であれば、内部に格納する数値は、バイナリの「科学表記法」を使用した純粋なバイナリ表現です。したがって、10進数10.75dを格納するように指示した場合、10には1010b、10進数には.11bが格納されます。したがって、.101011を保存し、最後に数ビットを保存します:小数点を4桁右に移動します。

(技術的にはもはや小数点ではありませんが、現在はバイナリポイントになっていますが、この用語は、あらゆる用途のこの答えを見つけるほとんどの人にとって、物事をより理解しやすくするものではありませんでした。)

22
Bill K

==を使用して浮動小数点値を比較することの何が問題になっていますか?

0.1 + 0.2 == 0.3が真実ではないため

19
AakashM

フロート(およびダブル)には多くの混乱があると思いますが、それを解消するのは良いことです。

  1. IDとしてフロートを使用することに本質的に問題はありません標準に準拠したJVMで [*]。 float IDを単にxに設定し、何もせず(つまり、算術演算を行わない)、後でy == xをテストすると、問題ありません。また、それらをHashMapのキーとして使用しても何も問題はありません。あなたができないことは、x == (x - y) + yなどのような平等を仮定することです。これは言われているように、人々は通常整数型をIDとして使用します。慣習に従うことをお勧めします。長いdoubleと同じ数の異なるvalues値があるため、doubleを使用しても何も得られないことに注意してください。また、「次の利用可能なID」を生成するには、doubleを使用するのが難しい場合があり、浮動小数点演算に関するある程度の知識が必要です。トラブルの価値はありません。

  2. 一方、数学的に等価な2つの計算結果の数値的同等性に依存することは危険です。これは、10進数から2進数表現に変換する際の丸め誤差と精度の損失が原因です。これはSOで死に至るまで議論されてきました。

[*]「標準に準拠したJVM」と言ったとき、特定の脳損傷JVM実装を除外したかった。 this を参照してください。

11
quant_dev

これはJavaに固有の問題ではありません。 ==を使用して2つのfloats/doubles/any decimal型の数値を比較すると、格納方法が原因で問題が発生する可能性があります。単精度浮動小数点数(IEEE規格754に準拠)には32ビットがあり、次のように分散されます。

1ビット-符号(0 =正、1 =負)
8ビット-指数(2 ^ xのxの特別な(バイアス127)表現)
23ビット-マンティサ。格納される実際の番号。

カマキリが問題の原因です。これは科学表記法のようなもので、基数2(バイナリ)の数値のみが1.110011 x 2 ^ 5またはそれに似たものに見えます。ただし、バイナリでは、最初の1は常に1です(0の表現を除く)

したがって、メモリ空間を少し節約するために(意図したしゃれ)、IEEEは1を想定することを決定しました。たとえば、1011のカマキリは実際には1.1011です。

これは、比較でいくつかの問題を引き起こす可能性があります。0はフロートで正確に表現できない可能性があるため、特に0で発生します。これが、他の回答で説明されている浮動小数点演算の問題に加えて、==が推奨されない主な理由です。

Javaには、言語が多くの異なるプラットフォーム間で普遍的であり、それぞれが独自の浮動小数点形式を持つことができるという点で、固有の問題があります。 ==を避けることがさらに重要になります。

2つのフロート(言語固有ではない)が等しいかどうかを比較する適切な方法は次のとおりです。

if(ABS(float1 - float2) < ACCEPTABLE_ERROR)
    //they are approximately equal

victorが既に述べたように、ACCEPTABLE_ERRORは#definedまたは0.000000001に等しいその他の定数、または必要な精度です。

一部の言語には、この機能またはこの定数が組み込まれていますが、一般的にはこれが適切な習慣です。

8

丸め誤差のため、浮動小数点値は信頼できません。

そのため、それらはおそらくsectionIDなどのキー値として使用すべきではありません。代わりに整数を使用するか、longに十分な値が含まれていない場合はintを使用します。

8
Eric Wilson

以前の回答に加えて、-0.0fおよび+0.0f(これらは==ですが、equalsではありません)およびFloat.NaNに関連する奇妙な動作があることに注意してください。 (それはequalsであるが、==ではない)(私はそれが正しいことを望んでいる-それはしないでください!)。

編集:確認しましょう!

import static Java.lang.Float.NaN;
public class Fl {
    public static void main(String[] args) {
        System.err.println(          -0.0f   ==              0.0f);   // true
        System.err.println(new Float(-0.0f).equals(new Float(0.0f))); // false
        System.err.println(            NaN   ==               NaN);   // false
        System.err.println(new Float(  NaN).equals(new Float( NaN))); // true
    }
} 

IEEE/754へようこそ。

現在、それを行うための迅速で簡単な方法は次のとおりです。

if (Float.compare(sectionID, currentSectionID) == 0) {...}

ただし、 docs は、マージンの差の値(@Victorの答えからのepsilon)を明確に指定していません。 floatの計算には常に存在しますが、標準言語ライブラリの一部であるため、合理的なものでなければなりません。

しかし、より高い精度またはカスタマイズされた精度が必要な場合は、

float epsilon = Float.MIN_NORMAL;  
if(Math.abs(sectionID - currentSectionID) < epsilon){...}

別のソリューションオプションです。

7
alisa

以下は、この問題や発生する可能性のある他の多くの浮動小数点問題についての非常に長い(しかし、うまくいけば役立つ)議論です。 すべてのコンピューター科学者が浮動小数点演算について知っておくべきこと

6
Adam Goode

Float.floatToIntBits()を使用できます。

Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)
4
aamadmi

以下は自動的に最高の精度を使用します。

/**
 * Compare to floats for (almost) equality. Will check whether they are
 * at most 5 ULP apart.
 */
public static boolean isFloatingEqual(float v1, float v2) {
    if (v1 == v2)
        return true;
    float absoluteDifference = Math.abs(v1 - v2);
    float maxUlp = Math.max(Math.ulp(v1), Math.ulp(v2));
    return absoluteDifference < 5 * maxUlp;
}

もちろん、5つより多いまたは少ないULP(「最後の場所のユニット」)を選択できます。

Apache Commonsライブラリを使用している場合、PrecisionクラスにはcompareTo()equals()があり、イプシロンとULPの両方が含まれています。

4
Michael Piefel

まず第一に、彼らはフロートまたはフロートですか?それらの1つがFloatの場合、equals()メソッドを使用する必要があります。また、おそらく静的なFloat.compareメソッドを使用するのが最善です。

4
omerkudat

あなたは==にしたいかもしれませんが、123.4444444444443!= 123.4444444444442

3
KM.

フロートを使用する必要がある場合は、strictfpキーワードが役立つ場合があります。

http://en.wikipedia.org/wiki/strictfp

3
sdcvvc

等しい実数を生成する2つの異なる計算は、必ずしも等しい浮動小数点数を生成しません。 ==を使用して計算結果を比較する人は、通常これに驚かされるので、警告は、さもなければ微妙で再現が難しいバグをフラグ付けするのに役立ちます。

2
VoiceOfUnreason

SectionIDとcurrentSectionIDという名前の物にフロートを使用するアウトソースコードを扱っていますか?ちょっと興味があるんだけど。

@Bill K: "floatのバイナリ表現はちょっと面倒です。"どうして?どのように改善しますか?終了することはないため、どのベースでも適切に表現できない特定の番号があります。 Piは良い例です。近似することしかできません。より良い解決策がある場合は、インテルにお問い合わせください。

2
xcramps

他の回答で述べたように、ダブルスにはわずかな偏差があります。また、「許容可能な」偏差を使用してそれらを比較する独自の方法を作成できます。しかしながら ...

doubleを比較するためのApacheクラスがあります: org.Apache.commons.math3.util.Precision

いくつかの興味深い定数が含まれています:SAFE_MINおよびEPSILONは、単純な算術演算の最大の可能な偏差です。

また、doubleを比較、等しい、または丸めるために必要なメソッドも提供します。 (ulpsまたは絶対偏差を使用)

1
bvdb

私が言うことができる1行の答えで、あなたは使うべきです:

Float.floatToIntBits(sectionID) == Float.floatToIntBits(currentSectionID)

関連する演算子を正しく使用する方法についてさらに学習するために、ここでいくつかのケースを詳しく説明します。一般に、Javaで文字列をテストするには3つの方法があります。 == 、. equals()、またはObjects.equals()を使用できます。

それらはどう違いますか? ==文字列の参照品質のテストは、2つのオブジェクトが同じかどうかを調べることを意味します。一方、.equals()は、2つの文字列が論理的に等しい値であるかどうかをテストします。最後に、Objects.equals()は、2つの文字列のNULLをテストし、.equals()を呼び出すかどうかを決定します。

使用する理想的なオペレーター

3人のオペレーターにはそれぞれ独自の長所と短所があるため、これについては多くの議論が行われています。たとえば、==はオブジェクト参照を比較する場合に推奨されるオプションですが、文字列値も比較するように見える場合があります。

ただし、Javaは値を比較しているように見えますが、実際にはそうではないという錯覚を引き起こすため、取得されるのはフォールズ値です。以下の2つのケースを検討してください。

事例1:

String a="Test";
String b="Test";
if(a==b) ===> true

ケース2:

String nullString1 = null;
String nullString2 = null;
//evaluates to true
nullString1 == nullString2;
//throws an exception
nullString1.equals(nullString2);

そのため、設計対象の特定の属性をテストするときは、各演算子を使用する方が適切です。しかし、ほとんどすべての場合、Objects.equals()はより普遍的な演算子であるため、Web開発者はそれを選択します。

ここで詳細を取得できます。 http://fluentthemes.com/use-compare-strings-Java/

1
Fluent-Themes

正しい方法は

Java.lang.Float.compare(float1, float2)
0
Eric