例如.:
Trunc(1470724508.0318);
实际上,实际代码更复杂:
ns: Real; v: Int64; ns := ((HighPerformanceTickCount*1.0)/g_HighResolutionTimerFrequency) * 1000000000; v := Trunc(ns);
但最终还是归结为:
Trunc(ARealValue);
现在,我不能在别的地方重复 – 就在这个地方.每次失败的地方
这不是巫毒
幸运的是电脑不是魔术.英特尔cpu执行非常具体的可观察的操作.所以我应该能够弄清楚为什么浮点运算失败.
进入cpu窗口
v := Trunc(ns)
06003
这将加载ebp-$10的8字节浮点值浮点寄存器ST0.
内存地址[ebp- $10]中的字节为:
0018E9D0: 6702098C 41D5EA5E (as DWords) 0018E9D0: 41D5EA5E6702098C (as QWords) 0018E9D0: 1470724508.0318 (as Doubles)
调用成功,浮点寄存器包含适当的值:
call @TRUNC
@TRUNC:
06006
或者我想我可以从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操作期间发生异常.
fistp qword ptr [esp+$04]
在此呼叫的时刻,ST0寄存器将包含相同的浮点值:
Note: The careful observer will note the value in the above screenshot doesn’t match the first screenshot. That’s because i took it on a different run. I’d rather not have to carefully redo all the constants in the question just to make them consistent – but trust me: it’s the same when i reach the
fistp
instruction as it was after thefld
instruction.
领导:
> sub esp,$0c:我看着它把堆栈推下12个字节
> fstcw word ptr [esp]:我看到它将$027F推入当前的堆栈指针
> fldcw word ptr [cwChop]:我看到浮点控制标志改变
> fistp qword ptr [esp $04]:它即将将Int64写入堆栈中的空间
然后它崩溃.
这里真的会发生什么?
它也发生在其他值,它不像这个特定的浮点值有问题.但是我甚至试图在其他地方设置测试用例.
知道float的8字节十六进制值是:$41D5EA5E6702098C,我试图设计设置:
var ns: Real; nsOverlay: Int64 absolute ns; v: Int64; begin nsOverlay := $41d62866a2f270dc; v := Trunc(ns); end;
这使:
nsOverlay := $41d62866a2f270dc;
060010
v := Trunc(ns)
060011
在调用@trunc时,浮点寄存器ST0包含一个值:
但是呼叫并没有失败.它只会失败,每次在我的代码的这一部分.
可能会发生什么可能导致cpu抛出无效的浮点异常?
加载控制字前,cwChop有什么价值?
加载控制字$1F32之前,cwChop的值看起来是正确的.但加载后,实际控制字错误:
奖金喋喋不休
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单元测试期间调用它的东西.
浮点控制字标志
德尔福的标准控制词:$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
加载$1F32后的CTRL标志:$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屏蔽的事实,这意味着您必须自己观看.像Win32Check或RaiseLastWin32Error一样,我们想要一个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(异常)标志被设置
我曾经试图把这个问题作为另外一个问题,但这将是完全一样的问题 – 除了这个时候,我们要回答Round().
解决方法
看看FPU窗口,肯定有错误. IE和PE标志都被设置.这是IE.这意味着在代码序列中较早的时候,屏蔽了无效操作.
然后调用Trunc来修改控制字以取消屏蔽异常.看看你的第二个FPU窗口截图. IE是1,但IM是0.所以繁荣,早期的异常被提出,你被认为是Trunc的错误.不是.
您需要跟踪调用堆栈,以了解为什么控制字不是Delphi程序中应该是什么.它应该是$1332.很可能你正在调用一些修改控制字的第三方库,而不是还原它.每当任何对该功能的呼叫返回时,您都必须找到歹徒并负责.
一旦控制词回到控制之下,你会发现这个异常的真正原因.显然有一个非法的FP操作.一旦控制字解除异常,错误就会在正确的位置上升.
请注意,没有任何担心$1372和$1332之间的差异,或$1F72和$1F32.这只是一个奇怪的CTRL控制字,一些字节被保留,忽略你的劝告以清除它们.