2009-08-17 17 views
3

Delphi 2009では、TObjectにGetHashCode関数が追加されました。 GetHashCodeは、TDictionaryのハッシュに使用されるIntegerを返します。DelphiでGetHashCodeのdouble型を整数に変換する

オブジェクトがTDictionaryでうまく動作するようにするには、GetHashCodeを適切にオーバーライドして、一般に異なるオブジェクトが異なる整数ハッシュコードを返すようにする必要があります。

しかし、ダブルフィールドを含むオブジェクトではどうしますか?どのようにGetHashCodeの整数にこれらのdouble値を変換しますか?

Javaで通常行われる方法は、Double.doubleToLongBitsやFloat.floatToIntBitsなどのメソッドを使用することです。後者には、次のようなドキュメントがあります。「IEEE 754浮動小数点「単一形式」ビットレイアウトに従って、指定された浮動小数点値の表現を返します。これは、浮動小数点値の異なるビットに対する異なるマスクを用いたいくつかのビット演算を含む。

Delphiでこれを行う関数はありますか?

+0

なぜ変更する必要がありますか?既定のGetHashCodeは、オブジェクトのメモリアドレスを返します。メモリアドレスは、定義によって各オブジェクトごとに一意です。 –

+1

オブジェクトが辞書のキーとして機能するようにするには、Equalsをオーバーライドする場合はGetHashCodeをオーバーライドする必要があると思います。時には、同じインスタンスであるかどうかをテストするだけでなく、2つのオブジェクトが等しいかどうかをテストするためにオブジェクトのフィールドを比較するためにEqualsをオーバーライドしたい場合もあります。 –

答えて

5

私はGamecatコードに比べて次のような改善をお勧めしたい:

type 
    TVarRec = record 
    case Integer of 
     0: (FInt1, FInt2 : Integer;) 
     1: (FDouble : Double;) 
    end; 

function Convert(const ADouble: Double): Integer; 
var 
    arec : TVarRec; 
begin 
    arec.FDouble := ADouble; 
    Result := arec.FInt1 xor arec.FInt2; 
end; 

これは、アカウントに、すべてのビットを取ります

(コメントはコードでうまく機能しません)

+2

優れています。 –

+0

改善のおかげで;-)。 –

2

あなたは整数にダブルをマッピングする場合は、バリアントレコードを使用することができます。

type 
    TVarRec = record 
    case Integer of 
     0: (FInt : Integer;) 
     1: (FDouble : Double;) 
    end; 


function Convert(const ADouble: Double): Integer; 
var 
    arec : TVarRec; 
begin 
    arec.FDouble := ADouble; 
    Result := arec.FInt; 
end; 

がこの値の解釈せずにビット単位のコピーを行うことに注意してください。

別(ダーティトリックの種類、絶対的な変数を使用している:

function Convert(const ADouble: Double): Integer; 
var 
    tempDouble : Double; 
    tempInt : Integer absolute tempDouble; // tempInt is at the same memory position as tempDouble. 
begin 
    tempDouble := ADouble; 
    Result := tempInt; 
end; 
+0

ありがとうございます。これらのメソッドはいくつかの倍精度数値ではうまくいくようですが、同じ整数値を与える倍精度数値がたくさんあります。例えば、すべての整数が整数値0を与えるように見える。ハッシュコードが同一である可能性を減らすために、これを何らかの形で改善することは可能でしょうか?それとも、私は規則的な数字のパターンでテストしているからですか? –

+0

sizeof(double)= 8、sizeof(integer)= 4なので、うまくいきません.2つの整数を2倍にマッピングすることもできます... –

0

GetHashCodeのデフォルト値は、オブジェクトごとに一意であることが保証されている数値、つまりオブジェクトのメモリアドレスを返すので、このようなことをする必要はありません。さらに、オブジェクトに含まれるデータを変更すると、デフォルトのハッシュ値は変更されません。

値3.5のDoubleを含むオブジェクトをハッシュして辞書に入れ、ハッシュコード12345678を取得したとしましょうDoubleフィールドは変更され、現在は5.21という値が得られています。次にハッシュ値を計算しようとすると、ハッシュコードが23456789になり、検索が失敗します。

これが起こらないことを保証できない限り、メモリアドレスを使用しない本当の理由がある場合は、GetHashCodeをそのまま残すことをお勧めします。 (それが壊れていない場合は、それを修正しないでください。)

+0

実際には、変更可能なオブジェクトをハッシュテーブルのキーとして使用し、ハッシュテーブル内にある間にそれらを変更すると問題が発生すると思います。しかし、私はあなたがEqualsをオーバーライドする場合、GetHashCodeをオーバーライドする必要があると思います。とにかくそれはJavaで動作する方法です。その点でDelphiについて何か違うことはありますか(私はDelphiについて言えばそれだけではありません)?私が理解しているように、ハッシュテーブル(およびおそらくTDictionary)は、通常、GetHashCodeとEqualsの両方に依存して特定のアイテムを特定します。 –

+0

それは両方に依存しますが、それらを独立して使用します。他のものを変更するときに変更する必要はありません。値の使用方法の詳細については、TDictionary .ContainsValueおよびTDictionary .GetBucketIndexを参照してください。 –

+0

ええ、はい、あなたはそこにいると思います。 GetBucketIndexはTDictionaryの内部でFComparerを使用していますが、デフォルトではID比較が行われるように見えます。 そのため、Javaでは "等しいオブジェクトは等しいハッシュコードを持つ必要があります"というルールは、hashCodeを多くオーバーライドする必要があるということを意味しますが、それはDelphiではそうではないようです...それはいいですよね:) –

0

私は、Javaのことは、このようにDelphiで実装することができますね。

type 
    TVarRec = record 
    case Integer of 
     0: (FInt1: Integer;) 
     1: (FSingle: Single;) 
    end; 

function GetHashCode(Value: Double): Integer; 
var 
    arec: TVarRec; 
begin 
    arec.FSingle := Value; 
    Result := arec.FInt1; 
end; 

の背後にある考え方は、ダブルの精度を削減することですInteger(Sizeof(Single)= Sizeof(Integer))のバイナリサイズと一致する値。あなたの値が衝突なしで単精度で表現できる場合、これは良いハッシュ値を与えます。

編集:D2009でタイプキャストがコンパイルされないため、私はバリアントレコードソリューションを採用しました。

+0

値がシングルが許す最大サイズよりも大きい場合は、悪い考えではありませんが、問題はありませんか?また、同じ整数値に丸めた倍数が多い場合は、重複したハッシュコードがたくさんあります。 –

+0

あなたの値はわかりませんが、MaxSingle = 3.4e + 38、これはかなりです。値は整数に丸められず、整数にキャストされるため、衝突の確率はあまり高くありません。整数への単一キャストは同じビット表現を持ちますが、整数値はまったく意味を持ちません。 –

0

xorが悪いので、ダブルデータでCRC32を使用してください。

program Project1; 

{$APPTYPE CONSOLE} 

uses 
    SysUtils; 

type 
    TVarRec = record 
    case Integer of 
     0: (FInt1, FInt2 : Integer;); 
     1: (FDouble : Double;); 
    end; 

function Convert(const ADouble: Double): Integer; 
var 
    arec : TVarRec; 
begin 
    arec.FDouble := ADouble; 
    Result := arec.FInt1 xor arec.FInt2; 
end; 

var 
    FDoubleVar1, FDoubleVar2: TVarRec; 
    HashCode1, HashCode2: Integer; 
begin 
    // Make a Double 
    FDoubleVar1.FInt1 := $DEADC0DE; 
    FDoubleVar1.FInt2 := $0C0DEF00; 

    // Make another Double 
    FDoubleVar2.FInt1 := $0C0DEF00; 
    FDoubleVar2.FInt2 := $DEADC0DE; 

    WriteLn('1rst Double : ', FDoubleVar1.FDouble); 
    WriteLn('2nd Double : ', FDoubleVar2.FDouble); 

    HashCode1 := Convert(FDoubleVar1.FDouble); 
    HashCode2 := Convert(FDoubleVar2.FDouble); 

    WriteLn('1rst HashCode : ', HashCode1); 
    WriteLn('2nd HashCode : ', HashCode2); 

    if HashCode1 = HashCode2 then 
    begin 
    WriteLn('Warning: Same HashCode!'); 
    end; 
    ReadLn; 
end. 
+0

ここに理由がありますか? – jpfollenius

+0

違うDoublesがxorのために同じハッシュコードを与える上記の例を追加しました。 – pani

+1

ハッシュテーブルは常に衝突につながります。特にダブルスの場合は、すべての可能なハッシュ値のセットよりもはるかに大きなセットです。 – jpfollenius

関連する問題