Delphi和Free Pascal中的SQRT函数有多精确?

Delphi和Free Pascal中的SQRT函数有多精确?,delphi,fpc,sqrt,Delphi,Fpc,Sqrt,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

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;
过程Test1(值:基数);
变量
根:基数;
开始
根:=Trunc(Sqrt(值));
断言(根*根值);
结束;
程序测试2(值:UInt64);
变量
根:UInt64;
开始
根:=Trunc(Sqrt(值));
断言(根*根值);
结束;

实践多于理论:

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

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.
程序项目1;
{$APPTYPE控制台}
{$R*.res}
使用
System.SysUtils;
{$IFNDEF DEBUG}
{$DEFINE DEBUG}
{$ENDIF}
程序Test1(值:基数);
变量
根:基数;
开始
根:=Trunc(Sqrt(值));
断言(根*根值);
结束;
程序测试2(值:UInt64);
变量
根:UInt64;
开始
根:=Trunc(Sqrt(值));
断言(根*根值);
结束;
变量
VCar:红衣主教;
VUInt:UInt64;
常数
Limit1:Cardinal=$FFFFFFFF;
Limit2:UInt64=$ffffffffffffff;
开始
尝试
对于VCar:=0到极限1 do
开始
如果(VCar模块10000000)=0,则
Writeln('VCarTest',VCar',,(VCar/Limit1*100):0:2',%');
Test1(VCar);
结束;
Writeln('VCarTest 0..$',IntToHex(Limit1,8),'passed');
{已注释,因为无法在合理的时间内执行
VUInt:=0;
而(VUInt=Limit2);
Writeln('VUIntTest',VUInt);
Writeln(“全部通过”);
除了
在E上:EAssertionFailed do
Writeln('value',VUInt',root base',RUInt的断言失败);
关于E:Exception-do
Writeln(E.ClassName,“:”,E.Message);
结束;
Readln;
结束。

您的测试不完整。test1的上限应为$FFFFFFFF,test2的上限应为$ffffffffffff,并包含上限值。此外,还需要编译器版本和位数(32位或64位编译器)。使用XE3进行测试(根据@Serge的注释更改限制)表明此操作失败(Win64,XE3编译器版本17.0.4770.56661)。它在Test1处失败,值为4294836225,这似乎是合理的。+1提供了一个很好的开始基础(并在答案中提供的范围内更正)。XE3中的实现(指示64位编译器版本,用
{$IFDEF CPUX64}
定义)是
SQRTSD XMM0,XMM0
,它也解决了问题的
FPU函数
部分。(32位版本使用
FSQRT
)我已经更新了测试代码。测试1在Delphi XE中通过,测试2无法在合理的时间内执行并进行了注释。@Serg:“引发新问题”意味着您应该发布一个“新问题”.;-)你可能应该这样发布。正如你最后的评论所说,这篇文章“回答了这个问题”。这似乎变成了聊天,在这里不合适。我仍然认为这个答案值得我的投票,我现在就不谈了。我希望你能得到一个答案,知道你希望在这里学到什么。祝你好运。:-)注意x64上的扩展是一个64位的浮点值。你能解释一下为什么你认为这些断言是正确的吗应该保持?还有if测试的目的是什么?为什么要截断Sqrt()的结果?只是为了看看Sqrt()是否太大,导致其不准确度超过+1.0?为什么不测量实际错误并报告准确的最大值?
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.