A conversão de um Casal para um número Inteiro de GetHashCode em Delphi
-
18-09-2019 - |
Pergunta
Delphi 2009, que acrescentou o GetHashCode função de TObject.GetHashCode retorna um número Inteiro que é usado para o hash em TDictionary.
Se você deseja um objeto para funcionar bem em TDictionary, você precisará substituir GetHashCode adequadamente de tal forma que, em geral, diferentes objetos de retorno diferentes inteiro códigos de hash.
Mas o que fazer para objectos que contenham campos duplas?Como transformar esses valores duplos em uma inteiros para GetHashCode?
A forma como é normalmente feito em Java, dizem, é a utilização de um método, como Dupla.doubleToLongBits ou Float.floatToIntBits.O último tem a documentação que descreve-a da seguinte forma:"Retorna uma representação do especificado valor de ponto flutuante de acordo com a norma IEEE 754 de ponto flutuante "único" formato de bits de layout." Isto envolve algumas operações bit a bit com máscaras diferentes para os diferentes bits de um valor de ponto flutuante.
Existe uma função que faz isso no Delphi?
Solução
Eu sugeriria a seguinte melhoria em relação ao código 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;
Isso leva em consideração todos os bits do valor duplo.
(Os comentários não funcionam bem com o código)
Outras dicas
Se você deseja mapear um casal para um inteiro, você pode usar uma variante do registro:
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;
Tenha em atenção que esta faz uma cópia bit a bit sem interpretação dos valores.
Outro (tipo de truque sujo, está usando absoluta variáveis:
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;
Realmente não há necessidade de fazer algo assim, porque o valor padrão do GethashCode já retorna um número que é garantido para cada objeto: o endereço de memória do objeto. Além disso, o valor do hash padrão não vai alterar se você alterar os dados que seu objeto contém.
Digamos que você tenha um objeto que contenha um dobro com um valor de 3,5, e você o hash e o coloca em um dicionário, e você recebe um código de hash de 12345678. Você também tem algo mais segurando uma referência e esse dobro O campo é alterado e agora tem um valor de 5,21. Da próxima vez que você tentar calcular seu valor de hash, seu código de hash é agora 23456789 e sua pesquisa falhará.
A menos que você possa garantir que isso nunca acontecerá e você tem um bom motivo para não usar o endereço de memória, sua melhor aposta é deixar o GethashCode como é. (Se não estiver quebrado, não conserte.)
Eu acho que a coisa do java pode ser implementada em Delphi como esta:
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;
A idéia por trás é reduzir a precisão do valor duplo para corresponder ao tamanho binário de um número inteiro (sizeof (único) = sizeof (número inteiro)). Se seus valores puderem ser representados em uma única precisão sem colisão, isso dará um bom valor de hash.
EDIT: Como o TypeCast não compilará no meu D2009, adaptei a solução de registro variante.
Use o CRC32 nos dados duplos porque xor é mau.
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.