extended - variables delphi 7
Delphi Tokyo 64-bit vacía números normales a cero? (2)
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.
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.
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 ya que los compiladores de 32 bits y de 64 bits deberían producir el mismo resultado. Double
y el compilador definitivamente debería adaptarse en consecuencia.
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) ?