delphi – 无效的浮点运算调用Trunc()
当我尝试Trunc()一个实数值时,我得到一个(可重复的)浮点异常.
例如.: Trunc(1470724508.0318); 实际上,实际代码更复杂: ns: Real; v: Int64; ns := ((HighPerformanceTickCount*1.0)/g_HighResolutionTimerFrequency) * 1000000000; v := Trunc(ns); 但最终还是归结为: Trunc(ARealValue); 现在,我不能在别的地方重复 – 就在这个地方.每次失败的地方 这不是巫毒 幸运的是电脑不是魔术.英特尔CPU执行非常具体的可观察的操作.所以我应该能够弄清楚为什么浮点运算失败. 进入CPU窗口
这将加载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函数的内容:
或者我想我可以从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寄存器将包含相同的浮点值:
领导: > sub esp,$0c:我看着它把堆栈推下12个字节 然后它崩溃. 这里真的会发生什么? 它也发生在其他值,它不像这个特定的浮点值有问题.但是我甚至试图在其他地方设置测试用例. 知道float的8字节十六进制值是:$41D5EA5E6702098C,我试图设计设置: var ns: Real; nsOverlay: Int64 absolute ns; v: Int64; begin nsOverlay := $41d62866a2f270dc; v := Trunc(ns); end; 这使:
在调用@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 222221 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(无效操作)标志置位 我曾经试图把这个问题作为另外一个问题,但这将是完全一样的问题 – 除了这个时候,我们要回答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控制字,一些字节被保留,忽略你的劝告以清除它们. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |