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


177

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

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

if(sectionID == currentSectionID)

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

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


29
floatと==の比較は問題があるため、IDとして使用するのは賢明ではありません。コード例の名前は、それがあなたがやっていることを示唆しています。long整数(long)が推奨され、IDの事実上の標準です。
Carl Manaster、2009


4
ええ、それは単なるランダムな例でしたか、実際にフロートをIDとして使用しましたか?理由はありますか?
Wiklanderによる2010

5
「floatフィールドの場合はFloat.compareメソッドを使用します。doubleフィールドの場合はDouble.compareを使用します。floatフィールドとdoubleフィールドの特別な処理は、Float.NaN、-0.0fおよび類似のdouble定数の存在によって必要になります。詳細については、Float.equalsのドキュメントをご覧ください。」(ジョシュア・ブロッホ:効果的なJava)
lbalazscs 2014年

回答:


211

floatが「等しい」かどうかをテストする正しい方法は次のとおりです。

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

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


26
固定イプシロンが常に良い考えとは限らない理由については、承認された回答のリンク(cygnus-software.com/papers/comparingfloats/comparingfloats.htm)を参照してください。具体的には、比較されるフロートの値が大きく(または小さく)なると、イプシロンは適切ではなくなります。(ただし、float値がすべて比較的妥当であることがわかっている場合は、イプシロンを使用しても問題ありません。)
PT

1
@PT彼はイプシロンを1つの数値で乗算しif(Math.abs(sectionID - currentSectionID) < epsilon*sectionID、その問題に取り組むために関数をに変更できますか?
enthusiasticgeek 14年

3
これは今のところ最良の答えであるかもしれませんが、それでも欠陥があります。イプシロンはどこから入手できますか?
Michael Piefel、2015

1
@MichaelPiefelはすでに言っています:「望ましい精度に依存します」。フロートは、その性質上、物理的な値のようなものです。全体の不正確さに応じて、限られた数の位置のみに関心があり、それを超える差異はすべて無効と見なされます。
ivan_pozdeev

しかし、OPは本当に等価性をテストすることだけを望んでおり、これは信頼できないことがわかっているため、別の方法を使用する必要があります。それでも、私は彼が彼の「望ましい精度」が何であるかさえ知っているとは思いません。したがって、より信頼性の高い同等性テストが必要な場合は、問題が残ります。イプシロンはどこから入手できますか?私はMath.ulp()この質問への私の回答で使用することを提案しました。
Michael Piefel

53

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

登録に関するこの記事では、これが当てはまる理由の概要を説明しています。有用で興味深い読書。


@kevindtimm:それで、(number == 6.099999904632568359375)数を知りたいときはいつでも6.1と等しい場合に平等テストを実行します...はい、あなたは正しいです...コンピュータ内のすべてが厳密に決定的です、浮動小数点数に使用される近似は、数学の問題を実行するときに直観に反するものです。
ニュートピア2009

浮動小数点値は、非常に特定のハードウェアでは不確定的にのみ不正確です。
スチュアートP.ベントレー

1
@Stuart私は誤解されたかもしれませんが、FDIVバグが非決定的であったとは思いません。ハードウェアによって与えられた答えは仕様に準拠していませんでしたが、同じ計算で常に同じ誤った結果が生成されるという点で、それらは確定的でした
Gravity

@Gravity特定の一連の警告があれば、どのような動作も確定的であると主張できます。
スチュアートP.ベントレー

浮動小数点は不正確ではありません。すべての浮動小数点値はまさにその値です。不正確である可能性があるのは、浮動小数点計算の結果です。しかし注意してください!プログラムに0.1のようなものが表示された場合、それは浮動小数点値ではありません。これは浮動小数点リテラルです ---コンパイラが計算を行うことによって浮動小数点値に変換する文字列。
ソロモンスロー

22

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

フロートのバイナリ表現は、ちょっと面倒です。

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

まあそれは逆にも働きます。

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

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

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

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

(技術的には小数点ではなくなりましたが、現在は2進小数点ですが、この用語を使用しても、この使用法の答えが見つかるほとんどの人にとって、理解が深まることはありません。)


1
@Matt K-ええと、固定小数点ではありません。「最後に数ビットを保存して、小数点[N]ビットを右に移動する」とすると、浮動小数点になります。固定小数点は、基数ポイントの位置を取得し、固定します。また、一般に、バイナマル(?)ポイントをシフトすると、常に左端の位置に「1」を残すことができるため、先頭の「1」を省略して解放されたスペース(1ビット!)指数の範囲を拡張します。
JustJeff 2009

この問題は、2進数表現と10進数表現では関係ありません。10進浮動小数点を使用しても、(1/3)* 3 == 0.9999999999999999999999999999のようになります。
dan04

2
@ dan04はい、1/3には10進数または2進数表現がないため、3進数表現があり、正しく変換されます:)。私がリストした数値(.1、.25など)はすべて完全な10進表記ですが、2進表記はありません。人々は「正確な」表記に慣れています。BCDはそれらを完全に処理します。それが違いです。
ビルK

1
これは、問題の背後にあるREALの問題を説明しているので、もっと多くの賛成投票が必要です。
Levite、2015年

19

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

それは真実ではないので 0.1 + 0.2 == 0.3


7
どうFloat.compare(0.1f+0.2f, 0.3f) == 0ですか?
Aquarius Power

0.1f + 0.2f == 0.3d、ただし0.1d + 0.2d!= 0.3d。デフォルトでは、0.1 + 0.2はdoubleです。0.3もdoubleです。
burnabyRails

12

浮動小数点数(および倍精度浮動小数点数)については多くの混乱があると思いますが、それを解消するのは良いことです。

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

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

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


floatをIDとして使用==する場合はequals、ではなくを使用して比較されるように注意する必要があります。そうでない場合、それ自体と等しくないfloatがテーブルに格納されないようにします。そうでない場合、たとえば、さまざまな入力が与えられたときに式から生成できる一意の結果の数を数えようとするプログラムは、すべてのNaN値を一意と見なす場合があります。
スーパーキャット2014

上記はを指すのであってFloat、を指すのではありませんfloat
quant_dev 2017

何の話Float?一意のfloat値のテーブルを作成し、それらをと比較しようとすると==、恐ろしいIEEE-754比較ルールにより、テーブルがNaN値でいっぱいになります。
スーパーキャット2017

floatタイプにはequalsメソッドがありません。
quant_dev 2017

ああ、私はequalsインスタンスメソッドではなく、Floattypeの2つの値を比較する静的メソッド(クラス内だと思います)を意味していましたfloat
スーパーキャット2017

9

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

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

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

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

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

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

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

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

ここで、ACCEPTABLE_ERRORは#definedであるか、0.000000001に等しい他の定数、またはビクターがすでに述べたように必要な精度です。

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


3
Javaには、フロートの動作が定義されています。プラットフォームに依存しません。
Yishai、2010年

9

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

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

ただし、ドキュメント ではマージンの差の値(イプシロン 、浮動小数点数の計算に常に存在 @Victorの回答からが、標準言語ライブラリの一部であるため、妥当なものでなければなりません。

さらに高い精度やカスタマイズされた精度が必要な場合は、

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

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


1
リンクしたドキュメントには、「f1がf2と数値的に等しい場合の値0」と記載されている(sectionId == currentSectionId)ため、浮動小数点では正確ではありません。イプシロン法がより良いアプローチであり、これはこの答えにあります:stackoverflow.com/a/1088271/4212710
typoerrpr

8

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

そのため、sectionIDなどのキー値として使用しないでください。代わりに整数を使用してください。またはlongint十分な可能性のある値が含まれていない場合。


2
同意した。これらがIDであることを考えると、浮動小数点演算で物事を複雑にする理由はありません。
Yohnny 2009

2
または長い。今後生成される一意のIDの数によっては、intが十分に大きくない場合があります。
ウェインハートマン、

floatと比較してdoubleはどのくらい正確ですか?
Arvindh Mani 2017

1
@ArvindhMani doubleの方がはるかに正確ですが、浮動小数点値でもあるので、私の答えはfloatとの両方を含めることを意味していましたdouble
Eric Wilson

7

前回の回答に加えて、あなたは、関連付けられた奇妙な行動があることを認識すべきである-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へようこそ。


何かが==の場合、それらはビットまで同じです。どうしてequals()にならないのでしょうか?多分あなたはそれを逆に持っていますか?
mkb、2009

@Matt NaNは特別です。JavaのDouble.isNaN(double x)は、実際には{return x!= x;として実装されています。} ...
quant_dev '06 / 07/19

1
floatを使用==しても、数値が「ビットと同一」であることを意味しません(同じ数値を異なるビットパターンで表すことができますが、正規化された形式は1つだけです)。同様に、-0.0fおよび0.0fは異なるビットパターンで表されます(符号ビットは異なります)が、と等しい==(ただしとは異なる)と比較しequalsます。==ビット比較であるというあなたの仮定は、一般的に言って、間違っています。
Pavel Minaev 2009

6

これと、発生する可能性のあるその他の多くの浮動小数点の問題についての非常に長い(ただし、うまくいけば役立つ)ディスカッションを次に示します。


5

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

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

1
あなたは正しい軌道に乗っています。floatToIntBits()は正しい方法ですが、Floatの組み込みのequals()関数を使用する方が簡単です。こちらをご覧ください:stackoverflow.com/a/3668105/2066079。デフォルトのequals()がfloatToIntBitsを内部で使用していることがわかります。
dberm22 14

1
はい、それらがFloatオブジェクトであれば可能です。プリミティブには上記の式を使用できます。
aamadmi 14

4

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


4

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

/**
 * 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;
}

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

あなたは、Apache Commonsのライブラリに興味があれば、Precisionクラスが持っているcompareTo()equals()イプシロンとULPの両方を持ちます。


floatをdoubleに変更すると、このメソッドはisDoubleEqual(0.1 + 0.2-0.3、0.0)== falseとして機能しません
hychou

これdoubleをカバーするための要因として、10_000_000_000_000_000Lのようなものがさらに必要であるようです。
マイケルピエフェル2017

3

==にすることもできますが、123.4444444444443!= 123.4444444444442



2

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


2

sectionIDおよびcurrentSectionIDという名前のフロートを使用するアウトソーシングコードを扱っていますか?ちょっと興味があるんだけど。

@Bill K:「フロートのバイナリ表現はちょっと面倒です。」どうして?どうすればもっと上手くなりますか?終わらないため、どの基数でも適切に表現できない特定の数値があります。Piは良い例です。あなたはそれを近似することができるだけです。より良い解決策がある場合は、インテルにお問い合わせください。


1

他の回答で述べたように、倍精度浮動小数点数にはわずかな偏差があります。そして、「許容できる」偏差を使用してそれらを比較する独自のメソッドを書くことができます。しかしながら ...

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

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

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


1

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

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

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

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

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

3つの演算子にはそれぞれ独自の長所と短所があるため、これは多くの議論の的となってきました。たとえば、==はオブジェクト参照を比較するときによく使用されるオプションですが、文字列値も比較しているように見える場合があります。

ただし、Javaは値を比較しているように見えますが、実際の意味ではそうではないので、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/


-2

正しい方法は

java.lang.Float.compare(float1, float2)

7
Float.compare(float1、float2)はintを返すため、if条件でfloat1 == float2の代わりに使用することはできません。さらに、この警告が参照している根本的な問題は実際には解決されません。浮動小数点数が数値計算の結果である場合、丸め誤差が原因でfloat1!= float2が発生する可能性があります。
quant_dev 2009

1
右、あなたはコピーして貼り付けることができません、あなたは最初にドキュメントをチェックする必要があります。
エリック、

2
あなたはFLOAT1 ==するfloat2あるFloat.compare(FLOAT1、するfloat2)== 0の代わりに何をすることができます
deterb

29
これはあなたに何かを購入しない-あなたはまだ取得Float.compare(1.1 + 2.2, 3.3) != 0
パベルMinaev

-2

丸め誤差を減らす1つの方法は、floatではなくdoubleを使用することです。これで問題がなくなることはありませんが、プログラムのエラーの量は減り、浮動小数点はほとんど最良の選択ではありません。私見では。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.