2009-08-17 10 views
3

Delphi 2009 a ajouté la fonction GetHashCode à TObject. GetHashCode renvoie un entier qui est utilisé pour le hachage dans TDictionary. Si vous voulez qu'un objet fonctionne correctement dans TDictionary, vous devez remplacer GetHashCode de façon appropriée, de sorte qu'en général, différents objets renvoient différents codes de hachage entiers.Conversion d'un double en entier pour GetHashCode dans Delphi

Mais que faites-vous pour les objets contenant des champs doubles? Comment transformez-vous ces valeurs doubles en nombres entiers pour GetHashCode?

La façon dont cela se fait habituellement en Java, par exemple, est d'utiliser une méthode comme Double.doubleToLongBits ou Float.floatToIntBits. Ce dernier dispose d'une documentation qui le décrit comme suit: "Renvoie une représentation de la valeur à virgule flottante spécifiée conformément à la disposition de bit" format unique "à virgule flottante IEEE 754." Cela implique des opérations au niveau du bit avec des masques différents pour les différents bits d'une valeur en virgule flottante.

Y at-il une fonction qui fait cela dans Delphi?

+0

Pourquoi avez-vous besoin de le changer? Le GetHashCode par défaut renvoie l'adresse mémoire de l'objet, qui est unique pour chaque objet par définition. –

+1

Je pense que vous devez remplacer GetHashCode si vous remplacez Equals, si vous voulez que les objets fonctionnent comme des clés dans un dictionnaire. Parfois, vous voulez remplacer Equals afin de comparer les champs de l'objet pour tester si deux objets sont égaux, par opposition à juste tester pour voir si elles sont exactement la même instance. –

Répondre

5

Je suggère l'amélioration suivante sur le code 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; 

Cela prend en compte tous les bits de la valeur double.

(commentaires ne fonctionnent pas bien avec le code)

+2

Excellent - cela semble fonctionner, merci :) –

+0

Merci pour l'amélioration ;-). –

2

Si vous souhaitez mapper un double à un nombre entier, vous pouvez utiliser un enregistrement variant:

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; 

Prenez garde que cela ne une copie sans interprétation des bitwise valeurs.

Un autre (genre de truc sale, utilise des variables absolues:

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

Merci Gamecat. Ces méthodes semblent bien fonctionner pour certains nombres doubles, mais vous obtenez beaucoup de nombres doubles qui donnent les mêmes nombres entiers. Par exemple, il semble que tous les nombres entiers donnent une valeur entière de zéro. Serait-il possible d'améliorer cela de façon à réduire les chances que les codes de hachage soient identiques? Ou est-ce juste parce que je teste avec des modèles réguliers de nombres? –

+0

Cela ne fonctionnera pas parce que sizeof (double) = 8, alors sizeof (integer) = 4. Vous pouvez mapper deux entiers sur le double si vous le souhaitez, cependant ... –

0

Il n'y a vraiment pas besoin de faire quelque chose comme ça, parce que la valeur par défaut de GetHashCode renvoie déjà un nombre qui est garanti pour être unique pour chaque objet: l'adresse mémoire de l'objet. En outre, la valeur de hachage par défaut ne va pas changer si vous modifiez les données que contient votre objet. Supposons que vous ayez un objet contenant un double avec une valeur de 3,5, que vous le hachez et le placiez dans un dictionnaire et que vous obteniez un code de hachage de 12345678. Vous avez également quelque chose d'autre contenant une référence , et ce champ double est changé et maintenant il a une valeur de 5,21. La prochaine fois que vous tenterez de calculer sa valeur de hachage, votre code de hachage est maintenant 23456789 et votre recherche échouera.

À moins que vous ne puissiez garantir que cela n'arrivera jamais, et vous avez une très bonne raison de ne pas utiliser l'adresse mémoire, le mieux est de laisser GetHashCode tel quel. (Si ce n'est pas cassé, ne le répare pas.)

+0

En effet, je pense que cela causerait des problèmes si vous utilisiez des objets mutables comme des clés dans une table de hachage, et que vous les modifiiez alors qu'ils étaient dans la table de hachage. Mais je pense que vous devez remplacer GetHashCode si vous remplacez Equals. C'est comme ça que ça fonctionne en Java quand même. Y at-il quelque chose de différent à propos de Delphi à cet égard (je ne suis pas tout à fait d'accord avec Delphi)? Si je comprends bien, hashtables (et probablement TDictionary) s'appuient généralement à la fois GetHashCode et Equals pour localiser un élément particulier. –

+0

Il s'appuie sur les deux, mais il s'appuie sur eux de manière indépendante. Il n'y a pas besoin de changer un quand vous changez l'autre. Voir TDictionary .ContainsValue et TDictionary .GetBucketIndex pour les détails sur la façon dont les valeurs sont utilisées. –

+0

Hmm oui je pense que vous êtes là. GetBucketIndex utilise FComparer, interne à TDictionary, mais par défaut cela ressemble à une comparaison d'identité. Donc, alors qu'en Java "les objets égaux doivent avoir des codes de hachage égaux" est la règle qui signifie que vous devez surcharger beaucoup hashCode, il semble que ce n'est pas le cas dans Delphi ... C'est bon :) –

0

Je pense que la chose Java peut être mis en œuvre dans Delphi comme ceci:

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; 

L'idée est derrière de réduire la précision de la Double valeur pour correspondre à la taille binaire d'un entier (Sizeof (Single) = Sizeof (Integer)). Si vos valeurs peuvent être représentées en simple précision sans collision, cela donnera une bonne valeur de hachage. Editer: Comme le typecast ne sera pas compilé dans mon D2009, j'ai adapté la solution d'enregistrement de variante.

+0

Pas une mauvaise idée, mais cela ne causerait-il pas des problèmes si la valeur est supérieure à la taille maximale autorisée par Single? De plus, si vous avez beaucoup de doubles arrondis à la même valeur Integer, vous obtiendrez beaucoup de hashcodes dupliqués. –

+0

Eh bien, je ne connais pas vos valeurs, mais MaxSingle = 3.4e + 38, ce qui est à peu près. La probalité des collisions n'est pas très élevée, car les valeurs ne sont pas arrondies aux entiers, mais sont converties en entiers. Une conversion unique en un entier a la même représentation de bits, mais la valeur de l'entier n'a aucune signification. –

0

Utilisez CRC32 sur les données Double car x ou est mauvaise.

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

Un raisonnement ici? – jpfollenius

+0

J'ai ajouté un exemple ci-dessus où différents doubles donnent le même code à cause de xor. – pani

+1

Les tables de hachage conduisent toujours à des collisions, en particulier pour les doubles - un ensemble beaucoup plus grand que l'ensemble de toutes les valeurs de hachage possibles. – jpfollenius

Questions connexes