Подтвердить что ты не робот

Дельфийский Токийский 64-бит сбрасывает денормальные числа до нуля?

Во время короткого взгляда на исходный код system.math я обнаружил, что 64-разрядная версия Delphi Tokyo 10.2.3 сбрасывает денормальные IEEE-Doubles в ноль, как видно из следующей программы;

{$apptype console}
uses
  system.sysutils, system.math;
var
  x: double;
const
  twopm1030 : UInt64 = $0000100000000000; {2^(-1030)}
begin
  x := PDouble(@twopm1030)^;
  writeln(x);
  x := ldexp(1,-515);
  writeln(x*x);
  x := ldexp(1,-1030);
  writeln(x);
end.

Для бита 32- выход так же, как ожидалось

8.69169475979376E-0311
8.69169475979376E-0311
8.69169475979376E-0311

но с 64-битным я получаю

 8.69169475979375E-0311
 0.00000000000000E+0000
 0.00000000000000E+0000

Таким образом, в основном Токио может обрабатывать денормальные числа в 64-битном режиме, константа написана правильно, но из арифметических операций или даже с ldexp денормальный результат сбрасывается до нуля.

Можно ли подтвердить это наблюдение на других системах? Если да, где это документировано? (Единственная информация, которую я мог найти о нулевом промывании, заключается в том, что Denormals become zero when stored in a Real48).

Обновление: Я знаю, что для обоих 32- и 64-бит используется единственная перегрузка. Для бита 32- используется FPU x87, и код ASM практически идентичен для всех прецизионностей (один, двойной, расширенный). FPU всегда возвращает 80-битный расширенный, который хранится в двойном без преждевременного усечения. 64-разрядный код выполняет настройку точности перед сохранением. Между тем я подал отчет о проблеме (https://quality.embarcadero.com/browse/RSP-20925), сосредоточив внимание на несогласованных результатах для 32- или 64-битных.

4b9b3361

Ответ 1

Обновление:

Существует только разница в том, как компилятор рассматривает перегруженный выбор.

Как выяснил @Graymatter, LdExp перегрузка LdExp является типом Single для 32-битного и 64-битного компилятора. Единственное различие - это код, где 32-разрядный компилятор использует код asm, в то время как 64-разрядный компилятор имеет реализацию purepascal.

Чтобы исправить код, чтобы использовать правильную перегрузку, явно укажите тип для первого аргумента LdExp() как это он работает (64-разрядный):

program Project116;

{$APPTYPE CONSOLE}
uses
  system.sysutils, system.math;
var
  x: double;
const
  twopm1030 : UInt64 = $0000100000000000; {2^(-1030)}
begin
  x := PDouble(@twopm1030)^;
  writeln(x);
  x := ldexp(Double(1),-515);
  writeln(x*x);
  x := ldexp(Double(1),-1030);
  writeln(x);
  ReadLn;
end. 

Выходы:

 8.69169475979375E-0311
 8.69169475979375E-0311
 8.69169475979375E-0311

Я бы сказал, что это поведение следует сообщать как ошибку RTL, так как перегруженная функция, выбранная в вашем случае, является типом Single. Получаемый тип - Double и компилятор должен определенно адаптироваться соответствующим образом. поскольку 32-разрядный и 64-битный компилятор должен получить тот же результат.


Примечание. В Delphi 10.2 Tokyo был введен метод Double(1) типа с плавающей точкой. Для решений в версиях prevoius см. Раздел " Что такое первая версия Delphi, которая позволяет приведениям типа double (10).

Ответ 2

Проблема здесь в том, что Ldexp(single) возвращает разные результаты в зависимости от того, вызывается ли код ASM или вызывается ли код pascal. В обоих случаях компилятор вызывает одиночную версию перегрузки, потому что тип не указан в вызове.

Ваш код pascal, который выполняется в сценарии Win64, пытается обрабатывать экспоненту меньше, чем -126, но метод все еще не может правильно вычислить результат, поскольку отдельные числа ограничены 8-разрядным показателем. Кажется, что ассемблер обошел это, но я не заглядывал в него подробно, почему это произошло.

function Ldexp(const X: Single; const P: Integer): Single;
  { Result := X * (2^P) }
{$IFNDEF X86ASM}
var
  T: Single;
  I: Integer;
const
  MaxExp =  127;
  MinExp = -126;
  FractionOfOne = $00800000;
begin
  T := X;
  Result := X;
  case T.SpecialType of
    fsDenormal,
    fsNDenormal,
    fsPositive,
    fsNegative:
      begin
        FClearExcept;
        I := P;
        if I > MaxExp then 
        begin
          T.BuildUp(False, FractionOfOne, MaxExp);
          Result := Result * T;
          I := I - MaxExp;
          if I > MaxExp then I := MaxExp;
        end
        else if I < MinExp then
        begin
          T.BuildUp(False, FractionOfOne, MinExp);
          Result := Result * T;
          I := I - MinExp;
          if I < MinExp then I := MinExp;
        end;
        if I <> 0 then
        begin
          T.BuildUp(False, FractionOfOne, I);
          Result := Result * T;
        end;
        FCheckExcept;
      end;
//    fsZero,
//    fsNZero,
//    fsInf,
//    fsNInf,
//    fsNaN:
  else
    ;
  end;
end;
{$ELSE X86ASM}
{$IF     defined(CPUX86) and defined(IOS)} // iOS/Simulator
...
{$ELSE} 
asm // StackAlignSafe
        PUSH    EAX
        FILD    dword ptr [ESP]
        FLD     X
        FSCALE
        POP     EAX
        FSTP    ST(1)
        FWAIT
end;
{$ENDIF}
{$ENDIF X86ASM}

Как предложил LU RD, вы можете обойти проблему, заставив методы вызвать двойную перегрузку. Есть ошибка, но эта ошибка заключается в том, что код ASM не соответствует коду Ldexp(const X: Single; const P: Integer) в Ldexp(const X: Single; const P: Integer), а не вызывает другую перегрузку.