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

前端之家收集整理的这篇文章主要介绍了delphi – 无效的浮点运算调用Trunc()前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
当我尝试Trunc()一个实数值时,我得到一个(可重复的)浮点异常.

例如.:

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)

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

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

call @TRUNC

接下来是Delphi RTL的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 the fld 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().

解决方法

问题发生在别的地方.当您的代码进入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相关文章