2013-03-09 2 views
3

SQRT реализован как функция FPU для 80-битного значения поплавка в Delphi XE; не уверен, как это реализовано в 64-битных компиляторах. Известно, что функции с плавающей точкой являются приблизительными.Насколько точна функция SQRT в Delphi и Free Pascal?

Могу ли я предположить, что следующие утверждения никогда не потерпят неудачу?

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; 
+0

только отметить, Extended на x64 является значение 64-битной плавающей точкой http://docwiki.embarcadero.com/ RADStudio/XE3/ru/Delphi_Considerations_for_Cross-Platform_Applications –

+2

Можете ли вы объяснить, почему вы считаете, что эти утверждения должны иметь место? Кроме того, для чего нужны тесты if? –

+0

Почему вы хотите усечь результаты Sqrt()? Просто чтобы убедиться, что SQRT() слишком велик по количеству, которое делает его нечестным более чем на +1.0? Почему бы не измерить реальные ошибки и сообщить точные максимумы? –

ответ

2

Больше практики, чем теории:

Выполните проверку на всех чисел, например:

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. 
+1

Ваши тесты неполны. Верхние пределы должны быть $ FFFFFFFF для test1 и $ FFFFFFFFFFFFFFFF для test2, при этом верхние значения включены. Также необходима версия компилятора и битность (32- или 64-разрядный компилятор). – kludg

+1

Тест с XE3 (изменение ограничений на основе комментария @ Serge) показывает, что это не удается (Win64, XE3 компилятор версии 17.0.4770.56661). Он терпит неудачу при тестировании1 со значением 4294836225, что кажется разумным. +1 для обеспечения отличной основы для начала (и исправления в пределах диапазона, указанного в ответе). –

+0

Реализация в XE3 (указанная версия 64-битного компилятора, определенная с помощью '{$ IFDEF CPUX64}'), представляет собой 'SQRTSD XMM0, XMM0', которая также обращается к функции' FPU function' вопроса. (32-разрядная версия использует 'FSQRT'.) –