Double を HashMap のキーとして使用することを考えていましたが、浮動小数点比較は安全ではないことを知っているので、考えさせられました。Double クラスの equals メソッドも安全ではありませんか? もしそうなら、それは hashCode メソッドもおそらく間違っていることを意味します。これは、Double を HashMap のキーとして使用すると、予期しない動作が発生することを意味します。
ここで私の憶測を確認できる人はいますか?
簡単な答え:やらないでください
長い答え:キーの計算方法は次のとおりです。
キーはオブジェクトでなければならないため、実際のキーはjava.lang.Double
オブジェクトになります。そのhashCode()
方法は次のとおりです。
public int hashCode() {
long bits = doubleToLongBits(value);
return (int)(bits ^ (bits >>> 32));
}
このdoubleToLongBits()
メソッドは基本的に 8 バイトを取り、それらを long として表します。したがって、double の計算における小さな変更が大きな意味を持つ可能性があり、重要なミスが発生する可能性があることを意味します。
ドットの後の特定のポイント数に落ち着くことができる場合は、10^(ドットの後の桁数) を掛けて、int に変換します (たとえば、2 桁の場合は 100 を掛けます)。
それははるかに安全になります。
私はあなたが正しいと思います。ダブルスのハッシュはintですが、ダブルスはハッシュを台無しにする可能性があります。そのため、JoshBlochがEffectiveJavaで言及しているように、ハッシュ関数への入力としてdoubleを使用する場合は、doubleToLongBits()を使用する必要があります。同様に、floatにはfloatToIntBitsを使用します。
特に、ハッシュとしてdoubleを使用するには、Josh Blochのレシピに従って、次のようにします。
public int hashCode() {
int result = 17;
long temp = Double.doubleToLongBits(the_double_field);
result = 37 * result + ((int) (temp ^ (temp >>> 32)));
return result;
}
これは、EffectiveJavaの項目8「equalsをオーバーライドするときは常にhashCodeをオーバーライドする」からのものです。それは本からの章のこのpdfで見つけることができます。
お役に立てれば。
それはあなたがそれをどのように使うかによります。
まったく同じビットパターン(または+/- 0やさまざまなNaNなどの同等のビットパターン)に基づいて値を見つけることしかできないことに満足している場合は、問題ない可能性があります。
特に、すべてのNaNは最終的には等しいと見なされますが、+0と-0は異なると見なされます。のドキュメントからDouble.equals
:
ほとんどの場合、クラスDoubleの2つのインスタンス、d1とd2の場合、d1.equals(d2)の値は、次の場合にのみtrueになります。
d1.doubleValue()== d2.doubleValue()の値もtrueです。ただし、2つの例外があります。
- d1とd2の両方がDouble.NaNを表す場合、Double.NaN == Double.NaNの値がfalseであっても、equalsメソッドはtrueを返します。
- d1が+0.0を表し、d2が-0.0を表す場合、またはその逆の場合、+ 0.0 ==-0.0の値がtrueであっても、等しいテストの値はfalseになります。
この定義により、ハッシュテーブルが適切に動作できるようになります。
ただし、「キーに非常に近い数字」に関心がある可能性が高いため、実行可能性は大幅に低下します。特に、キーを1回取得するために1セットの計算を実行し、次に2回目にキーを取得するために別のセットの計算を実行する場合、問題が発生します。
問題はハッシュコードではなく、doubleの精度です。これはいくつかの奇妙な結果を引き起こします。例:
double x = 371.4;
double y = 61.9;
double key = x + y; // expected 433.3
Map<Double, String> map = new HashMap<Double, String>();
map.put(key, "Sum of " + x + " and " + y);
System.out.println(map.get(433.3)); // prints null
計算された値(キー)は「433.29999999999995」であり、433.3と同等ではないため、マップにエントリが見つかりません(ハッシュコードもおそらく異なりますが、それが主な問題ではありません)。
使用する場合
map.get(key)
エントリが見つかるはずです...[]]
短い答え: おそらくうまくいきません。
正直な答え: それはすべて依存します。
より長い答え: ハッシュ コードは問題ではなく、浮動小数点での等値比較の性質です。Nalandial と彼の投稿のコメント投稿者が指摘しているように、最終的には、ハッシュ テーブルに対するすべての一致は、正しい値を選択するために equals を使用することになります。
問題は、equals が本当に equals を意味することを知っているような方法で double が生成されているかどうかです。値を読み取るか計算してハッシュ テーブルに格納し、後でまったく同じ計算を使用して値を読み取るか計算すると、Double.equals が機能します。1.2 + 2.3 は必ずしも 3.5 に等しいとは限らず、3.4999995 などに等しい可能性があります。(本当の例ではありません。私はそれをでっち上げましたが、それは実際に起こることのようなものです。) float と double は、少ないか多いかについて合理的に確実に比較できますが、等しい場合は比較できません。
マップの保存方法とアクセス方法によって異なります。同様の値はわずかに異なる可能性があるため、同じ値にハッシュされない可能性があります。
private static final double key1 = 1.1+1.3-1.6;
private static final double key2 = 123321;
...
map.get(key1);
しかし、すべて良いでしょう
map.put(1.1+2.3, value);
...
map.get(5.0 - 1.6);
危険だろう
double自体ではなく、doubleのハッシュが使用されます。
編集:ありがとう、ジョン、私は実際にそれを知りませんでした.
これについてはよくわかりませんが (Double オブジェクトのソース コードを参照するだけでよいでしょう)、浮動小数点の比較に関する問題は解決してくれると思います。