在仔细查看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位结果不一致。
答案 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
类型,因此。生成的类型为
因为32位和64位编译器应产生相同的结果。Double
,编译器一定会进行相应的调整。
注意,是浮点类型的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代码不匹配,而不是正在调用其他重载。