2

最近、Java で double のタプルのハッシュ コードを計算するときに奇妙な状況に遭遇しました。2 つのタプル (1.0,1.0) と (Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY) があるとします。Joshua Bloch の「Effective Java 」(項目 7) で述べられているイディオムを使用すると、これら 2 つのタプルは等しいとは見なされません (これらのタプルがオブジェクトであると想像してください)。hashCode()ただし、項目 8 に記載されている式を使用して各タプルを計算すると、同じ値に評価されます。

私の質問は次のとおりです。この式について、式を書いているときに見逃した何か奇妙なことがありますか?それとも、ハッシュコードの衝突の奇妙なケースですか?

状況を説明するための短い比較方法を次に示します (JUnit4 テストとして記述しましたが、mainメソッドに変換するのは非常に簡単です)。

@Test
public void testDoubleHashCodeAndInfinity(){
    double a = 1.0;
    double b = 1.0;
    double c = Double.POSITIVE_INFINITY;
    double d = Double.POSITIVE_INFINITY;

    int prime = 31;
    int result1 = 17;
    int result2 = 17;

    long temp1 = Double.doubleToLongBits(a);
    long temp2 = Double.doubleToLongBits(c);
    //this assertion passes successfully
    assertTrue("Double.doubleToLongBits(Double.POSITIVE_INFINITY" +
            "==Double.doubleToLongBits(1.0)",temp1!=temp2);

    result1 = prime*result1 + (int)(temp1^(temp1>>>32));
    result2 = prime*result2 + (int)(temp2^(temp2>>>32));

    //this assertion passes successfully 
    assertTrue("Double.POSITIVE_INFINITY.hashCode()" +
            "==(1.0).hashCode()",result1!=result2);

    temp1 = Double.doubleToLongBits(b);
    temp2 = Double.doubleToLongBits(d);
    //this assertion should pass successfully
    assertTrue("Double.doubleToLongBits(Double.POSITIVE_INFINITY" +
            "==Double.doubleToLongBits(1.0)",temp1!=temp2);

    result1 = prime*result1+(int)(temp1^(temp1>>>32));
    result2 = prime*result2+(int)(temp2^(temp2>>>32));

    //this assertion fails!
    assertTrue("(1.0,1.0).hashCode()==" +
            "(Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY).hashCode()",
            result1!=result2);
}
4

2 に答える 2

5

それはただの偶然です。しかし、それは興味深いものです。これを試して:

Double d1 = 1.0;
Double d2 = Double.POSITIVE_INFINITY;

int hash1 = d1.hashCode();
int hash2 = d2.hashCode();

// These both print -1092616192
// This was me using the wrong hash combinator *and*
// the wrong tuples... but it's interesting
System.out.println(hash1 * 17 + hash2);
System.out.println(hash2 * 17 + hash1);

// These both print -33554432
System.out.println(hash1 * 31 + hash1);
System.out.println(hash2 * 31 + hash2);

基本的に、ハッシュのビットパターンがこれを決定します。hash1 (1.0 のハッシュ コード) は 0x3ff00000 で、hash2 (無限のハッシュ コード) は 0x7ff00000 です。その種のハッシュとその種の乗数は、その種の効果を生み出します...

エグゼクティブサマリー:偶然ですが、心配しないでください:)

于 2009-12-24T21:12:44.143 に答える
0

偶然かもしれませんが、Map で hashCode を使用して、タプルに double を持つオブジェクトをキャッシュしようとしている場合、それは確かに役に立ちません。サーモスタットの一時設定クラスのマップを作成しているときに、これに遭遇しました。その後、hashCode をキーとして使用するときに Map から間違ったオブジェクトを取得していたため、他のテストが失敗していました。

これを修正するために私が見つけた解決策は、2 つの double パラメーターの追加された文字列を作成し、文字列で hashCode() を呼び出すことでした。String のオーバーヘッドを回避するために、ハッシュコードをキャッシュしました。

プライベートな揮発性 hashCode;
@Override public int hashCode()
{
  int 結果 = hashCode;
  もし (結果 == 0) {
     文字列値 = new StringBuilder().append(d1).append(d2).toString();
     結果 = value.hashCode();
     hashCode = 結果;
  }
  結果を返します。
}
于 2011-03-23T19:01:53.563 に答える