Преобразование Double в целое число для GetHashCode в Delphi
-
18-09-2019 - |
Вопрос
Delphi 2009 добавил функцию GetHashCode в TObject.GetHashCode возвращает целое число, которое используется для хеширования в TDictionary.
Если вы хотите, чтобы объект хорошо работал в TDictionary, вам нужно соответствующим образом переопределить GetHashCode, чтобы, как правило, разные объекты возвращали разные целочисленные хэш-коды.
Но что вы делаете для объектов, содержащих двойные поля?Как вы превращаете эти двойные значения в целые числа для GetHashCode?
Обычно это делается, скажем, в Java с использованием такого метода, как Double .Двойные длинные биты или Float.floatToIntBits.У последнего есть документация, которая описывает это следующим образом:"Возвращает представление указанного значения с плавающей запятой в соответствии с битовой разметкой IEEE 754 с плавающей запятой "single format"". Это включает в себя некоторые побитовые операции с различными масками для разных битов значения с плавающей запятой.
Есть ли функция, которая делает это в Delphi?
Решение
Я бы предложил следующее улучшение по сравнению с кодом 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;
При этом учитываются все биты двойного значения.
(комментарии плохо работают с кодом)
Другие советы
Если вы хотите сопоставить значение double с целым числом, вы можете использовать запись 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;
Имейте в виду, что при этом выполняется побитовое копирование без интерпретации значений.
Другой (своего рода грязный трюк) заключается в использовании абсолютных переменных:
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;
На самом деле нет необходимости делать что-то подобное, потому что значение GetHashCode по умолчанию уже возвращает число, которое гарантированно уникально для каждого объекта:адрес объекта в памяти.Кроме того, значение хэша по умолчанию не изменится, если вы измените данные, содержащиеся в вашем объекте.
Допустим, у вас есть объект, который содержит Double со значением 3.5, и вы хэшируете его и помещаете в словарь, и вы получаете хэш-код 12345678.У вас также есть что-то еще, содержащее ссылку на это, и это Двойное поле изменяется, и теперь оно имеет значение 5.21.В следующий раз, когда вы попытаетесь вычислить его хэш-значение, ваш хэш-код теперь равен 23456789, и ваш поиск завершится ошибкой.
Если вы не можете гарантировать, что этого никогда не произойдет, и у вас есть действительно веская причина не использовать адрес памяти, лучше всего просто оставить GetHashCode как есть.(Если он не сломался, не чините его.)
Я предполагаю, что 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;
Идея заключается в том, чтобы уменьшить точность двойного значения, чтобы оно соответствовало двоичному размеру целого числа (Sizeof(Single) = Sizeof(Integer)).Если ваши значения могут быть представлены с одинарной точностью без коллизий, это даст хорошее хэш-значение.
Редактировать:Поскольку приведение типов не будет компилироваться в моем D2009, я адаптировал решение variant record.
Используйте 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.