float - word delphi




Delphi Tokyo 64-bit vacía números normales a cero? (2)

El problema aquí es que Ldexp(single) está devolviendo resultados diferentes dependiendo de si se está llamando el código ASM o si se llama el código pascal. En ambos casos, el compilador está llamando a la versión única de la sobrecarga porque el tipo no está especificado en la llamada.

El código pascal que se ejecuta en el escenario Win64 intenta tratar con el exponente menor que -126, pero el método aún no puede calcular correctamente el resultado porque los números individuales están limitados a un exponente de 8 bits. El ensamblador parece sortear esto, pero no lo examiné en detalle por qué ese es el caso.

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}

Como sugirió LU RD, puede solucionar el problema forzando los métodos para llamar a la sobrecarga Doble. Hay un error pero ese error es que el código ASM no coincide con el código pascal en Ldexp(const X: Single; const P: Integer) , no que se esté llamando a una sobrecarga diferente.

Durante un breve vistazo al código fuente de system.math, descubrí que la versión de 64 bits de Delphi Tokyo 10.2.3 vacía a cero el IEEE denormal, como se puede ver en el siguiente programa;

{$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.

Para 32 bits la salida es la esperada.

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

pero con 64 bits me sale

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

Entonces, básicamente, Tokio puede manejar números denormales en modo de 64 bits, la constante se escribe correctamente, pero a partir de operaciones aritméticas o incluso con ldexp, un resultado denormal se descarga a cero.

¿Se puede confirmar esta observación en otros sistemas? Si es así, ¿dónde está documentado? (La única información que pude encontrar sobre el lavado a cero es que los Denormals become zero when stored in a Real48 ).

Actualización: sé que para la sobrecarga única de 32 y 64 bits se usa. Para 32 bits, se utiliza la FPU x87 y el código ASM es prácticamente idéntico para todas las precisiones (simple, doble, extendida). La FPU siempre devuelve una extensión de 80 bits que se almacena en un doble sin truncamiento prematuro. El código de 64 bits hace un ajuste de precisión antes de almacenar. Mientras tanto, presenté un informe de problemas ( https://quality.embarcadero.com/browse/RSP-20925 ), con el foco en los resultados inconsistentes para 32 o 64 bits.


Actualización :

Solo hay una diferencia en cómo el compilador trata la selección sobrecargada.

Como @Graymatter descubrió, la sobrecarga de LdExp llamada es el tipo Single tanto para el compilador de 32 bits como para el de 64 bits. La única diferencia es la base de código, donde el compilador de 32 bits está utilizando el código asm, mientras que el compilador de 64 bits tiene una implementación purepascal.

Para corregir el código para usar la sobrecarga correcta, defina explícitamente el tipo para el primer argumento LdExp() como este que funciona (64 bits):

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. 

Salidas:

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

Yo diría que este comportamiento debe informarse como un error RTL, ya que la función sobrecargada seleccionada en su caso es el tipo Single . El tipo resultante es un Double y el compilador definitivamente debería adaptarse en consecuencia. ya que los compiladores de 32 bits y de 64 bits deberían producir el mismo resultado.

Tenga en cuenta que el Double(1) typecast para tipos de punto flotante se introdujo en Delphi 10.2 Tokyo. Para obtener soluciones en las versiones anteriores, consulte ¿Qué es la primera versión de Delphi que permite tipografías como double (10) ?