Насколько точна функция SQRT в Delphi и Free Pascal?
SQRT
реализован как функция FPU для 80-битного значения с плавающей запятой в Delphi XE; не уверен, как это реализовано в 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;
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.
Так как для тестирования всего диапазона для 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.