Domanda

Delphi 2009 aggiunge la funzione GetHashCode a TObject. GetHashCode restituisce un intero che viene utilizzato per l'hashing in TDictionary.

Se si desidera un oggetto per lavorare bene in TDictionary, è necessario eseguire l'override GetHashCode in modo appropriato in modo tale che, in generale, diversi oggetti restituiscono diversi codici di hash intero.

Ma cosa fai per gli oggetti che contengono doppi campi? Come si fa a trasformare quei valori in un doppio interi per GetHashCode?

Il modo in cui è fatto solitamente in Java, per esempio, è quello di utilizzare un metodo come Double.doubleToLongBits o Float.floatToIntBits. Quest'ultimo ha una documentazione che descrive come segue: "restituisce una rappresentazione del valore a virgola mobile specificato secondo lo standard IEEE 754 in virgola mobile 'formato single' la layout bit." Ciò comporta alcune operazioni bit per bit con diverse maschere per i diversi bit di un valore in virgola mobile.

C'è una funzione che fa questo in Delphi?

È stato utile?

Soluzione

Io suggerirei il seguente miglioramento rispetto al codice 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;

Questo tiene conto di tutti i bit del valore doppio.

(commenti non funzionano bene con codice)

Altri suggerimenti

Se si desidera mappare una doppia in un intero, è possibile utilizzare un record variante:

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;

Attenzione che questo fa una copia bit per bit, senza interpretazione dei valori.

Un altro (tipo di sporco trucco, sta usando le variabili assolute:

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;

Non c'è davvero alcun bisogno di fare qualcosa di simile, perché il valore di default di GetHashCode restituisce già un numero che è garantito per essere unico per ogni oggetto: indirizzo di memoria dell'oggetto. Inoltre, il valore hash di default non è destinato a cambiare se si modificano i dati l'oggetto contiene.

Diciamo che avete un oggetto che contiene un doppio con un valore di 3,5, e si hash e metterlo in un dizionario, e si ottiene un codice hash di 12345678. Hai anche qualcosa d'altro in possesso di un riferimento ad esso, e che doppio campo viene cambiato e ora che ha un valore di 5.21. La prossima volta che si tenta di calcolare il suo valore hash, il codice hash è ora 23.456.789, e la vostra ricerca avrà esito negativo.

A meno che non si può garantire che questo non accadrà mai, e si dispone di una buona ragione per non utilizzare l'indirizzo di memoria, la soluzione migliore è quella di lasciare solo GetHashCode così com'è. (Se non è rotto, non aggiustarlo.)

Credo che la cosa Java può essere implementato in Delphi in questo modo:

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'idea è dietro di ridurre la precisione del valore doppio in base al formato binario di un intero (Sizeof (Single) = Sizeof (Integer)). Se i valori possono essere rappresentati in precisione singola senza collisioni, questo darà un buon valore di hash.

Modifica:. Come il typecast non compilerà nella mia D2009, ho adattato la soluzione di registrazione variante

Usa CRC32 sui dati doppie perché xor è il male.

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.
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top