【问题标题】:How accurate is SQRT function in Delphi and Free Pascal?Delphi 和 Free Pascal 中的 SQRT 函数有多准确?
【发布时间】:2013-03-09 03:11:09
【问题描述】:

SQRT 在 Delphi XE 中作为 80 位浮点值的 FPU 函数实现;不确定它是如何在 64 位编译器中实现的。众所周知,浮点函数是近似的。

我可以假设下一个断言永远不会失败吗?

procedure Test1(Value: Cardinal);
var
  Root: Cardinal;

begin
  Root:= Trunc(Sqrt(Value));
  Assert(Root * Root <= Value);
  if Root < $FFFF then
    Assert((Root + 1) * (Root + 1) > Value);
end;

procedure Test2(Value: UInt64);
var
  Root: UInt64;

begin
  Root:= Trunc(Sqrt(Value));
  Assert(Root * Root <= Value);
  if Root < $FFFFFFFF then
    Assert((Root + 1) * (Root + 1) > Value);
end;

【问题讨论】:

  • 只是要注意在 x64 上扩展是一个 64 位浮点值 docwiki.embarcadero.com/RADStudio/XE3/en/…
  • 你能解释一下为什么你认为这些断言应该成立吗?还有什么是 if 测试?
  • 为什么要截断 Sqrt() 的结果?只是想看看 SQRT() 是否过大以至于使其不准确超过 +1.0 ?为什么不测量真正的错误并报告准确的最大值?

标签: delphi fpc sqrt


【解决方案1】:

实践胜于理论:

对所有数字进行测试,如下所示:

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

{$IFNDEF DEBUG}
  {$DEFINE DEBUG}
{$ENDIF}

procedure Test1(Value: Cardinal);
var
  Root: Cardinal;

begin
  Root:= Trunc(Sqrt(Value));
  Assert(Root * Root <= Value);
  if Root < $FFFF then
    Assert((Root + 1) * (Root + 1) > Value);
end;

procedure Test2(Value: UInt64);
var
  Root: UInt64;

begin
  Root:= Trunc(Sqrt(Value));
  Assert(Root * Root <= Value);
  if Root < $FFFFFFFF then
    Assert((Root + 1) * (Root + 1) > Value);
end;

var
  VCar: Cardinal;
  VUInt: UInt64;
const
  Limit1: Cardinal = $FFFFFFFF;
  Limit2: UInt64 = $FFFFFFFFFFFFFFFF;
begin
  try
    for VCar := 0 to Limit1 do
    begin
      if (VCar mod 10000000) = 0 then
        Writeln('VCarTest ', VCar, ' ', (VCar / Limit1 * 100):0:2, '%');
      Test1(VCar);
    end;
    Writeln('VCarTest 0 .. $', IntToHex(Limit1, 8), ' passed');
{ commented because cannot be executed in a reasonable time
    VUInt := 0;
    while (VUInt <= Limit2) do
    begin
      if (VUInt mod 2500000) = 0 then
        Writeln('VUIntTest ', VUInt, ' ', (VUInt / Limit2 * 100):0:2, '%');
      Test2(VUInt);
      Inc(VUInt);
    end;
    Writeln('VUIntTest ', VUInt);
    Writeln('All passed');
}

  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

由于确实需要 ages 来测试 UInt64 的整个范围,所以我稍微改变了测试以测试所有完美的正方形,之前的数字和每个之后的数字,只是为了让它更快并有一个更好的主意。我个人在 32 位上运行了一段时间没有失败(占整个测试的 1%),在 64 位上它很快就显示失败。我仍在寻找更接近此的方法,但我发布了代码以防万一您感兴趣:

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

{$IFNDEF DEBUG}
  {$message error 'change your build configuration to Debug!'}
{$ENDIF}

procedure Test2(Value: UInt64);
var
  Root: UInt64;
begin
//try/except block only for 64 bits, since in 32 bits it makes the process much slower
{$ifdef CPUX64}
  try
{$endif}
    Root:= Trunc(Sqrt(Value));
    Assert(Root * Root <= Value);
    if Root < $FFFFFFFF then
      Assert((Root + 1) * (Root + 1) > Value);
{$ifdef CPUX64}
  except
    Writeln('Fails for value: ', Value, ' root: ', Root
      , ' test: ', (Root + 1) * (Root + 1));
    raise;
  end;
{$endif}
end;

var
  RUInt, VUInt: UInt64;

const
  Limit2: UInt64 = $FFFFFFFFFFF00000;
begin
  try
    RUInt := 1;
    repeat
      Inc(RUInt);
      VUInt := RUInt * RUInt;
      if (RUInt mod 2500000) = 0 then
        Writeln('VUIntTest ', VUInt, ' ', (VUInt / Limit2 * 100):0:4, '%');
      Test2(VUInt - 1);
      Test2(VUInt);
      Test2(VUInt + 1);
    until (VUInt >= Limit2);
    Writeln('VUIntTest ', VUInt);
    Writeln('All passed');
  except
    on E:EAssertionFailed do
      Writeln('The assertion failed for value ', VUInt, ' root base ', RUInt);
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

【讨论】:

  • 您的测试不完整。 test1 的上限应为 $FFFFFFFF,test2 的上限应为 $FFFFFFFFFFFFFFFF,包括上限。还需要编译器版本和位数(32 位或 64 位编译器)。
  • 使用 XE3 的测试(根据 @Serge 的评论更改限制)显示失败(Win64,XE3 编译器版本 17.0.4770.56661)。它在 Test1 处失败,值为 4294836225,这似乎是合理的。 +1 为开始提供了一个很好的基础(并在答案中提供的范围内更正)。
  • XE3(表示 64 位编译器版本,用{$IFDEF CPUX64} 定义)中的实现是SQRTSD XMM0, XMM0,它也解决了问题的FPU function 部分。 (32位版本使用FSQRT。)
  • 我已经更新了测试代码。 Test1 在 Delphi XE 中通过,Test2 无法在合理的时间内执行并被注释掉。
  • @Serg:“提出新问题”意味着您应该发布“新问题”。 ;-) 您可能应该这样发布它。正如您最后的评论所说,这篇文章“回答了这个问题”。这似乎变成了聊天,在这里不合适。我仍然认为这个答案值得我投赞成票,我暂时不谈。我希望你能得到一个答案,你希望在这里学到什么。祝你好运。 :-)
猜你喜欢
  • 1970-01-01
  • 2015-10-31
  • 2013-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-01
相关资源
最近更新 更多