gpt4 book ai didi

delphi - 无效的浮点运算调用Trunc()

转载 作者:行者123 更新时间:2023-12-03 14:35:55 42 4
gpt4 key购买 nike

当我尝试Trunc()一个Real值时,出现一个(可重复的)浮点异常。

例如。:

Trunc(1470724508.0318);


实际上,实际代码更加复杂:

 ns: Real;
v: Int64;

ns := ((HighPerformanceTickCount*1.0)/g_HighResolutionTimerFrequency) * 1000000000;
v := Trunc(ns);


但最终它仍然可以归结为:

Trunc(ARealValue);


现在,我无法在其他任何地方重复此操作-仅在此位置。每次失败的地方。

不是伏都教

幸运的是,计算机不是魔术。英特尔CPU执行非常具体的可观察动作。所以我应该能够弄清楚为什么浮点运算会失败。

进入CPU窗口


  v:= Trunc(ns)

fld qword ptr [ebp-$10]



这会将ebp- $ 10处的8字节浮点值加载到浮点寄存器 ST0中。

内存地址[ebp- $ 10]中的字节为:

0018E9D0: 6702098C 41D5EA5E    (as DWords)
0018E9D0: 41D5EA5E6702098C (as QWords)
0018E9D0: 1470724508.0318 (as Doubles)


调用成功,并且浮点寄存器包含适当的值:



接下来是对RTL Trunc函数的实际调用:

call @TRUNC


接下来是Delphi RTL的Trunc函数的内容:


  @TRUNC:

sub esp,$0c
wait
fstcw word ptr [esp] //Store Floating-Point Control Word on the stack
wait
fldcw word ptr [cwChop] //Load Floating-Point Control Word
fistp qword ptr [esp+$04] //Converts value in ST0 to signed integer
//stores the result in the destination operand
//and pops the stack (increments the stack pointer)
wait
fldcw word ptr [esp] //Load Floating-Point Control Word
pop ecx
pop eax
pop edx
ret



或者我想我可能只是从rtl粘贴了它,而不是从CPU窗口中转录它:

const cwChop : Word = $1F32;

procedure _TRUNC;
asm
{ -> FST(0) Extended argument }
{ <- EDX:EAX Result }

SUB ESP,12
FSTCW [ESP] //Store foating-control word in ESP
FWAIT
FLDCW cwChop //Load new control word $1F32
FISTP qword ptr [ESP+4] //Convert ST0 to int, store in ESP+4, and pop the stack
FWAIT
FLDCW [ESP] //restore the FPCW
POP ECX
POP EAX
POP EDX
end;


在实际的拳头操作期间会发生异常。

fistp qword ptr [esp+$04]


在此调用的时刻,ST0寄存器将包含相同的浮点值:




  注意:细心的观察者会注意到上面的屏幕截图中的值与第一个屏幕截图不匹配。那是因为我采取了另一种方式。我宁愿不必仔细地重做问题中的所有常量以使它们保持一致-但请相信我:到达 fistp指令时与在 fld指令后相同。


导致它:


sub esp,$0c:我看着它将堆栈压低了12个字节
fstcw word ptr [esp]:我看到它将$ 027F推入当前堆栈指针
fldcw word ptr [cwChop]:我看着浮点控制标志改变
fistp qword ptr [esp+$04]:它将把Int64写入它在堆栈中创建的房间


然后它崩溃了。

实际上这里会发生什么?

它也与其他值一起发生,这并不意味着此特定的浮点值有问题。但是我什至尝试在其他地方设置测试用例。

知道浮点数的8个字节的十六进制值为: $41D5EA5E6702098C,我试图进行设置:

var
ns: Real;
nsOverlay: Int64 absolute ns;
v: Int64;
begin
nsOverlay := $41d62866a2f270dc;
v := Trunc(ns);
end;


这使:


  nsOverlay:= $ 41d62866a2f270dc;

mov [ebp-$08],$a2f270dc
mov [ebp-$04],$41d62866

  
  v:= Trunc(ns)

fld qword ptr [ebp-$08]
call @TRUNC



call@trunc的点,浮点寄存器ST0包含一个值:



但是通话不会失败。每次在我的代码的这一部分中,它只会失败。

可能导致CPU抛出 invalid floating point exception的情况是什么?

cwChop加载控制字之前的值是什么?

cwChop的值看起来在负载控制字 $1F32之前是正确的。但是加载后,实际的控制字是错误的:



奖金Chat不休

失败的实际功能是将高性能滴答计数转换为纳秒的东西:

function PerformanceTicksToNs(const HighPerformanceTickCount: Int64): Int64; 
//Convert high-performance ticks into nanoseconds
var
ns: Real;
v: Int64;
begin
Result := 0;

if HighPerformanceTickCount = 0 then
Exit;

if g_HighResolutionTimerFrequency = 0 then
Exit;

ns := ((HighPerformanceTickCount*1.0)/g_HighResolutionTimerFrequency) * 1000000000;

v := Trunc(ns);
Result := v;
end;


我创建了所有intermeidate临时变量,以尝试跟踪失败的位置。

我什至试图用它作为模板来重现它:

var
i1, i2: Int64;
ns: Real;
v: Int64;
vOver: Int64 absolute ns;
begin
i1 := 5060170;
i2 := 3429541;
ns := ((i1*1.0)/i2) * 1000000000;
//vOver := $41d62866a2f270dc;
v := Trunc(ns);


但这很好。关于在DUnit单元测试期间何时调用它的某些事情。

浮点控制字标志

Delphi的标准控制字: $1332

$1332 = 0001 00 11 00 110010
0 ;Don't allow invalid numbers
1 ;Allow denormals (very small numbers)
0 ;Don't allow divide by zero
0 ;Don't allow overflow
1 ;Allow underflow
1 ;Allow inexact precision
0 ;reserved exception mask
0 ;reserved
11 ;Precision Control - 11B (Double Extended Precision - 64 bits)
00 ;Rounding control -
0 ;Infinity control - 0 (not used)


The Windows API required value$027F

$027F = 0000 00 10 01 111111
1 ;Allow invalid numbers
1 ;Allow denormals (very small numbers)
1 ;Allow divide by zero
1 ;Allow overflow
1 ;Allow underflow
1 ;Allow inexact precision
1 ;reserved exception mask
0 ;reserved
10 ;Precision Control - 10B (double precision)
00 ;Rounding control
0 ;Infinity control - 0 (not used)


crChop控制字: $1F32

$1F32 = 0001 11 11 00 110010
0 ;Don't allow invalid numbers
1 ;Allow denormals (very small numbers)
0 ;Don't allow divide by zero
0 ;Don't allow overflow
1 ;Allow underflow
1 ;Allow inexact precision
0 ;reserved exception mask
0 ;unused
11 ;Precision Control - 11B (Double Extended Precision - 64 bits)
11 ;Rounding Control
1 ;Infinity control - 1 (not used)
000 ;unused


加载 CTRL后的 $1F32标志: $1F72

$1F72 = 0001 11 11 01 110010
0 ;Don't allow invalid numbers
1 ;Allow denormals (very small numbers)
0 ;Don't allow divide by zero
0 ;Don't allow overflow
1 ;Allow underflow
1 ;Allow inexact precision
1 ;reserved exception mask
0 ;unused
11 ;Precision Control - 11B (Double Extended Precision - 64 bits)
11 ;Rounding control
1 ;Infinity control - 1 (not used)
00011 ;unused


CPU所做的全部工作就是打开保留的未使用的屏蔽位。

RaiseLastFloatingPointError()

如果要为Windows开发程序,则确实需要接受以下事实:浮点异常应被CPU屏蔽,这意味着您必须自己注意它们。像 Win32CheckRaiseLastWin32Error,我们想要一个 RaiseLastFPError。我能想到的最好的是:

procedure RaiseLastFPError();
var
statWord: Word;
const
ERROR_InvalidOperation = $01;
// ERROR_Denormalized = $02;
ERROR_ZeroDivide = $04;
ERROR_Overflow = $08;
// ERROR_Underflow = $10;
// ERROR_InexactResult = $20;
begin
{
Excellent reference of all the floating point instructions.
(Intel's architecture manuals have no organization whatsoever)
http://www.plantation-productions.com/Webster/www.artofasm.com/Linux/HTML/RealArithmetica2.html

Bits 0:5 are exception flags (Mask = $2F)
0: Invalid Operation
1: Denormalized - CPU handles correctly without a problem. Do not throw
2: Zero Divide
3: Overflow
4: Underflow - CPU handles as you'd expect. Do not throw.
5: Precision - Extraordinarily common. CPU does what you'd want. Do not throw
}
asm
fwait //Wait for pending operations
FSTSW statWord //Store floating point flags in AX.
//Waits for pending operations. (Use FNSTSW AX to not wait.)
fclex //clear all exception bits the stack fault bit,
//and the busy flag in the FPU status register
end;

if (statWord and $0D) <> 0 then
begin
//if (statWord and ERROR_InexactResult) <> 0 then raise EInexactResult.Create(SInexactResult)
//else if (statWord and ERROR_Underflow) <> 0 then raise EUnderflow.Create(SUnderflow)}
if (statWord and ERROR_Overflow) <> 0 then raise EOverflow.Create(SOverflow)
else if (statWord and ERROR_ZeroDivide) <> 0 then raise EZeroDivide.Create(SZeroDivide)
//else if (statWord and ERROR_Denormalized) <> 0 then raise EUnderflow.Create(SUnderflow)
else if (statWord and ERROR_InvalidOperation) <> 0 then raise EInvalidOp.Create(SInvalidOp);
end;
end;


可复制的情况!

我发现了一个案例,当Delphi的默认浮点控制字出现时,这是无效浮点异常的原因(尽管我以前从未见过它,因为它被屏蔽了)。现在,我看到了,为什么会发生呢!它是可重现的:

procedure TForm1.Button1Click(Sender: TObject);
var
d: Real;
dover: Int64 absolute d;
begin
d := 1.35715152325557E020;
// dOver := $441d6db44ff62b68; //1.35715152325557E020
d := Round(d); //<--floating point exception
Self.Caption := FloatToStr(d);
end;


您可以看到 ST0寄存器包含有效的浮点值。浮点控制字是 $1372。浮点异常标志全部清除:



然后,它一旦执行便是无效操作:




设置了 IE(无效操作)标志
设置了 ES(Exception)标志


我很想再问另一个问题,但这将是完全相同的问题-除了这次调用 Round()

最佳答案

该问题发生在其他地方。当您的代码输入Trunc时,控制字将设置为$027F,即IIRC,即默认的Windows控制字。这掩盖了所有例外。这是一个问题,因为Delphi的RTL期望对异常进行屏蔽。

并查看FPU窗口,确定有错误。 IE和PE标志都已设置。最重要的是IE。这意味着在代码序列的前面,存在被屏蔽的无效操作。

然后,调用Trunc修改控制字以取消屏蔽异常。查看第二个FPU窗口截图。 IE为1但IM为0。所以繁荣,引发了更早的异常,您被认为是Trunc的错误。不是。

您需要追溯调用堆栈,以找出为什么控制字不是Delphi程序中应有的字。应该是$1332。您很可能正在调用某个第三方库,该库会修改控制字而不还原它。您必须找到罪魁祸首,并在对该函数的任何调用返回时负责。

将控制字放回原位后,您将找到导致此异常的真正原因。显然有非法的FP操作。一旦控制字消除了异常,将在正确的位置引发错误。

请注意,不必担心$1372$1332$1F72$1F32之间的差异。与CTRL控制字相比,这是一个奇怪的事情,其中​​有些字节是保留的,无视您的劝告以清除它们。

关于delphi - 无效的浮点运算调用Trunc(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22210233/

42 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com