在 Delphi 中将 GetHashCode 的双精度数转换为整数

发布于 2024-08-02 22:00:21 字数 404 浏览 7 评论 0原文

Delphi 2009为TObject添加了GetHashCode函数。 GetHashCode 返回一个 Integer,用于 TDictionary 中的散列。

如果您希望对象在 TDictionary 中正常工作,则需要适当地重写 GetHashCode,以便通常不同的对象返回不同的整数哈希代码。

但是对于包含双字段的对象该怎么办呢?如何将这些双精度值转换为 GetHashCode 的整数?

例如,在 Java 中通常完成的方式是使用 Double.doubleToLongBits 或 Float.floatToIntBits 等方法。后者的文档描述如下:“根据 IEEE 754 浮点“单一格式”位布局返回指定浮点值的表示形式。”这涉及对浮点值的不同位使用不同掩码的一些按位运算。

Delphi中有没有这样的函数?

Delphi 2009 added the GetHashCode function to TObject. GetHashCode returns an Integer which is used for hashing in TDictionary.

If you want an object to work well in TDictionary, you need to override GetHashCode appropriately such that, in general, different objects return different integer hash codes.

But what do you do for objects containing double fields? How do you turn those double values into a integers for GetHashCode?

The way it's usually done in Java, say, is to use a method like Double.doubleToLongBits or Float.floatToIntBits. The latter has documentation that describes it as follows: "Returns a representation of the specified floating-point value according to the IEEE 754 floating-point "single format" bit layout." This involves some bitwise operations with different masks for the different bits of a floating point value.

Is there a function that does this in Delphi?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

南街女流氓 2024-08-09 22:00:21

我建议对 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 值的所有位。

(注释与代码不能很好地配合)

I'd suggest the following improvement over the Gamecat code:

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;

This takes into account all the bits of the Double value.

(comments do not work well with code)

顾铮苏瑾 2024-08-09 22:00:21

如果要将双精度数映射到整数,可以使用变体记录:

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;

If you want to map a double to an integer, you can use a variant record:

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;

Beware that this does a bitwise copy without interpretation of the values.

Another (kind of dirty trick, is using absolute variables:

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;
手心的海 2024-08-09 22:00:21

实际上没有必要做这样的事情,因为 GetHashCode 的默认值已经返回一个保证每个对象都是唯一的数字:对象的内存地址。此外,如果您更改对象包含的数据,默认哈希值也不会更改。

假设您有一个包含值为 3.5 的 Double 的对象,您对它进行哈希处理并将其放入字典中,您将获得哈希码 12345678。您还有其他对象保存对它的引用,并且该 Double字段发生更改,现在其值为 5.21。下次您尝试计算其哈希值时,您的哈希代码现在为 23456789,并且您的查找将失败。

除非您可以保证这种情况永远不会发生,并且您有充分的理由不使用内存地址,否则最好的选择是保持 GetHashCode 不变。 (如果它没有坏,就不要修理它。)

There's really no need to do something like this, because the default value of GetHashCode already returns a number that's guaranteed to be unique for each object: the object's memory address. Furthermore, the default hash value isn't going to change if you change the data your object contains.

Let's say you have an object that contains a Double with a value of 3.5, and you hash it and put it into a dictionary, and you get a hash code of 12345678. You also have something else holding a reference to it, and that Double field gets changed and now it's got a value of 5.21. Next time you attempt to calculate its hash value, your hash code is now 23456789, and your lookup will fail.

Unless you can guarantee that this will never happen, and you have a really good reason not to use the memory address, your best bet is to just leave GetHashCode as it is. (If it ain't broke, don't fix it.)

我的痛♀有谁懂 2024-08-09 22:00:21

我想 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;

背后的想法是降低 Double 值的精度以匹配 Integer 的二进制大小 (Sizeof(Single) = Sizeof(Integer))。如果您的值可以用单精度表示而不会发生冲突,那么这将给出一个很好的哈希值。

编辑:由于类型转换无法在我的 D2009 中编译,我调整了变体记录解决方案。

I guess the Java thing can be implemented in Delphi like this:

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;

The idea behind is to reduce the precision of the Double value to match the binary size of an Integer (Sizeof(Single) = Sizeof(Integer)). If your values can be represented in Single precision without collision, this will give a good hash value.

Edit: As the typecast won't compile in my D2009, I adapted the variant record solution.

回忆躺在深渊里 2024-08-09 22:00:21

对 Double 数据使用 CRC32,因为 xor 是邪恶的。

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.

Use CRC32 on the Double data because xor is evil.

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.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文