Delphi Tokyo 64位会将非正规数刷新为零?

时间:2018-07-20 15:11:58

标签: delphi floating-point

在仔细查看system.math的源代码时,我发现 从随后的程序可以看出,Delphi Tokyo 10.2.3的64位版本将异常的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

因此,基本上Tokyo可以在64位模式下处理非正规数,可以正确写入常量,但是通过算术运算甚至使用ldexp,非正规结果都将刷新为零。

可以在其他系统上确认此观察结果吗?如果是,在哪里记录? (我能找到的有关零冲洗的唯一信息是, Denormals become zero when stored in a Real48)。

更新:我知道对于两者 32位和64位都使用单个重载。对于32位,使用x87 FPU,并且所有精度(单精度,双精度,扩展精度)的ASM代码实际上都是相同的。 FPU始终返回一个80位扩展名,该扩展名存储在double中,不会过早截断。 64位代码在存储之前进行精度调整。 同时,我提交了一个问题报告(https://quality.embarcadero.com/browse/RSP-20925),重点是32位或64位结果不一致。

2 个答案:

答案 0 :(得分:12)

更新

编译器对待重载选择的方式只有一个区别。

@Graymatter发现,对于32位和64位编译器,调用的LdExp重载是Single类型。唯一的区别是代码库,其中32位编译器使用asm代码,而64位编译器具有purepascal实现。

要修复代码以使用正确的重载,请像这样{64}显式定义LdExp()第一个参数的类型:

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位编译器应产生相同的结果。


注意,是浮点类型的Double(1)类型转换,已在Delphi 10.2 Tokyo中引入。有关prevoius版本的解决方案,请参见What is first version of Delphi which allows typecasts like double(10)

答案 1 :(得分:6)

这里的问题是Ldexp(single)返回不同的结果,具体取决于是否正在调用ASM代码或是否在调用pascal代码。在这两种情况下,编译器都在调用Single版本的重载,因为在调用中未指定类型。

您在Win64方案中执行的pascal代码试图处理小于-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所建议,可以通过强制方法调用Double重载来解决此问题。有一个错误,但是该错误是ASM代码与Ldexp(const X: Single; const P: Integer)中的pascal代码不匹配,而不是正在调用其他重载。