Преобразование Double в целое число для GetHashCode в Delphi

StackOverflow https://stackoverflow.com/questions/1288176

Вопрос

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.
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top