【问题标题】:Can I compare Real48 using generics.defaults?我可以使用 generics.defaults 比较 Real48 吗?
【发布时间】:2015-06-10 11:07:12
【问题描述】:

以下用于比较两个 Real48(6 字节浮点数)的代码编译并运行,但要么生成无意义的结果,要么生成 AV。

program Project44;

{$APPTYPE CONSOLE}
uses
  System.SysUtils,
  System.Generics.Defaults;

begin
  try
    WriteLn(System.Generics.Defaults.TComparer<Real48>.Default.Compare(100.0,100.0));
    WriteLn('all ok, press space');
  except on E:exception do
    WriteLn(e.Message);
  end;
  ReadLn
end.

它应该输出 0,但如果它没有先炸弹,它会输出 -92 或其他一些不正确的值。

这个错误仍然存​​在于最新的 XE8 中吗?
如果是这样,之前有没有报道过,我在https://quality.embarcadero.com 上找不到任何东西,但是如果有一个较旧的 QC,我想参考一下。

终于…… 如何使用TComparer&lt;something&gt; 比较两个REAL48 类型?

编辑:
这是我决定的解决方法:

interface
...snip...
[Test]
procedure TestReal48;
...snip...  
    TTest<T> = record
  private
    class var Def: System.Generics.Defaults.IComparer<T>;
    class var F: FastDefaults.TComparison<T>;
  public
    class function Real48Comparison(const Left, Right: T): Integer; static;

implementation

procedure TestDefault.TestReal48;
var
  OldDef: System.Generics.Defaults.IComparer<Real48>;
begin
  OldDef:= TTest<Real48>.Def;
  TTest<Real48>.Def:= System.Generics.Defaults.TComparer<Real48>.Construct(TTest<Real48>.Real48Comparison);
  TTest<Real48>.Test(100.0,100.0);
  TTest<Real48>.Test(100000.0,-10000.0);
  TTest<Real48>.Test(0.0,-10000.0);
  TTest<Real48>.Test(100000.0,0.0);
  TTest<Real48>.Test(0.0,0.0);
  TTest<Real48>.Def:= OldDef;
end;

【问题讨论】:

标签: delphi delphi-xe7 delphi-xe8


【解决方案1】:

此缺陷存在于所有版本的编译器中。由于 Real48 在十多年前已被弃用,我希望 Embarcadero 不会改变行为,即使您提交了错误报告。当然,您仍然应该提交错误报告,但在等待修复时我不会屏住呼吸!

您必须构建一个比较器,而不是依赖默认值:

var
  Comparer: IComparer<Real48>;

function Real48Comparison(const Left, Right: Real48): Integer;
begin
  if Left < Right then
    Result := -1
  else if Left > Right then
    Result := 1
  else
    Result := 0;
end;

Comparer := System.Generics.Defaults.TComparer<Real48>.Construct(Real48Comparison);

为什么默认的Real48 比较器失败得这么厉害。好吧,从这里开始:

class function TComparer<T>.Default: IComparer<T>;
begin
  Result := IComparer<T>(_LookupVtableInfo(giComparer, TypeInfo(T), SizeOf(T)));
end;

事实证明TypeInfo(Real48) 产生nilReal48 似乎没有可用的类型信息。可能不是一个很大的惊喜。

然后我们到达这里:

function _LookupVtableInfo(intf: TDefaultGenericInterface; info: PTypeInfo; size: Integer): Pointer;
var
  pinfo: PVtableInfo;
begin
  if info <> nil then
  begin
    pinfo := @VtableInfo[intf, info^.Kind];
    Result := pinfo^.Data;
    if ifSelector in pinfo^.Flags then
      Result := TTypeInfoSelector(Result)(info, size);
    if ifVariableSize in pinfo^.Flags then
      Result := MakeInstance(Result, size);
  end
  else
  begin
    case intf of
      giComparer: Result := Comparer_Selector_Binary(info, size);
      giEqualityComparer: Result := EqualityComparer_Selector_Binary(info, size);
    else
      System.Error(reRangeError);
      Result := nil;
    end;
  end;
end;

我们使用else 分支并致电Comparer_Selector_Binary。所以我们最终执行了二进制比较。比较实际上是由这个函数执行的:

function Compare_Binary(Inst: PSimpleInstance; const Left, Right): Integer;
begin
  Result := BinaryCompare(@Left, @Right, Inst^.Size);
end;

调用:

function BinaryCompare(const Left, Right: Pointer; Size: Integer): Integer;
var
  pl, pr: PByte;
  len: Integer;
begin
  pl := Left;
  pr := Right;
  len := Size;
  while len > 0 do
  begin
    Result := pl^ - pr^;
    if Result <> 0 then
      Exit;
    Dec(len);
    Inc(pl);
    Inc(pr);
  end;
  Result := 0;
end;

对于实值类型没有用处。

至于与Real48 的 ABI 相关的运行时错误。似乎Real48 参数总是在堆栈上传递。这与在Compare_Binary 中使用无类型参数不兼容。

【讨论】:

  • 无论如何修复很简单:任何未知typeinfo 6 字节长度的变量必须是Real48。我希望我们可以向 RTL 提交补丁。
  • 顺便说一句,这是一个漂亮的答案。
  • 我现在可以解释运行时错误了。 Real48 的特殊 ABI,与无类型参数不兼容。 FWIW 您的错误报告更多地表明问题是运行时错误。但我认为值得强调的是,二进制比较永远不会适合真实类型。
  • 更新了错误报告,你说得对,现在更清楚了。奇怪的是GetTypeInfo(Real48) 生成 tkFloat。所以在某种程度上,Real48 的 RTTi 似乎已经恢复。然而,由于 XE7 RTL 不使用GetTypeInfo(对其自身不利),因此它无法识别它是 tkFloat 而不是 tkUnknown 的事实。
  • 这只是 GetTypeKind 函数的结果。你不能用它做任何事情。事实上,它需要获取类型数据来获取 TFloatType,然后 aww,Real48 没有 FloatType。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-22
  • 1970-01-01
  • 2020-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多