Convertir un doble a un entero para GetHashCode en Delphi
-
18-09-2019 - |
Pregunta
Delphi 2009 añadió la función GetHashCode a TObject. GetHashCode devuelve un entero que se utiliza para hashing en TDictionary.
Si desea que un objeto para trabajar bien en TDictionary, es necesario reemplazar GetHashCode apropiadamente de manera que, en general, diferentes objetos devuelven diferentes códigos número entero de patata.
Pero, ¿qué hacer para objetos que contienen campos de dobles? ¿Cómo convertir esos valores en una doble enteros para GetHashCode?
La forma en que se realiza generalmente en Java, por ejemplo, es el uso de un método como Double.doubleToLongBits o Float.floatToIntBits. Este último tiene la documentación que lo describe de la siguiente manera: "Devuelve una representación del valor de punto flotante especificado de acuerdo con la norma IEEE 754 de punto flotante 'formato único' diseño poco." Esto implica algunas operaciones bit a bit con diferentes máscaras para los diferentes bits de un valor de punto flotante.
¿Hay una función que hace esto en Delphi?
Solución
Me gustaría sugerir la siguiente mejora sobre el 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;
Esto toma en cuenta todos los bits del valor doble.
(los comentarios no funcionan bien con el código)
Otros consejos
Si desea asignar un doble a un número entero, se puede utilizar un registro 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;
Tenga en cuenta que esto hace una copia bit a bit sin interpretación de los valores.
Otro (tipo de truco sucio, es el uso de variables absolutas:
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;
En realidad no hay necesidad de hacer algo como esto, debido a que el valor por defecto de GetHashCode ya se devuelve un número que se garantiza que sea único para cada objeto: dirección de memoria del objeto. Por otra parte, el valor hash por defecto no va a cambiar si cambia los datos de su objeto contiene.
Digamos que usted tiene un objeto que contiene un doble con un valor de 3,5, y hash y lo puso en un diccionario, y se obtiene un código hash de 12345678. También tiene algo más que sostiene una referencia a él, y ese campo se cambia doble y ahora que tiene un valor de 5,21. La próxima vez que intenta calcular su valor hash, su código hash es ahora 23.456.789, y su consulta de fallará.
A menos que usted puede garantizar que esto nunca sucederá, y tiene una muy buena razón para no usar la dirección de memoria, lo mejor es que acaba de salir de GetHashCode tal como es. (Si no está roto, no lo arregles.)
Creo que lo Java puede ser implementado en Delphi como esto:
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;
La idea detrás es reducir la precisión del valor doble para que coincida con el tamaño de un entero binario (sizeof (Individual) = sizeof (entero)). Si sus valores pueden ser representados en la precisión individual sin colisión, esto le dará un buen valor hash.
Editar:. A medida que el encasillado no se compilará en mi D2009, me he adaptado la solución registro variante
El uso de los datos CRC32 doble porque xor es el mal.
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.