Invalid floating point operation delphi как исправить

I’m getting a (repeatable) floating point exception when i try to Trunc() a Real value.

e.g.:

Trunc(1470724508.0318);

In reality the actual code is more complex:

 ns: Real;
 v: Int64;

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

But in the end it still boils down to:

Trunc(ARealValue);

Now, i cannot repeat it anywhere else — just at this one spot. Where it fails every time.

It’s not voodoo

Fortunately computers are not magic. The Intel CPU performs very specific observable actions. So i should be able to figure out why the floating point operation fails.

Going into the CPU window

v := Trunc(ns)

fld qword ptr [ebp-$10]

This loads the 8-byte floating point value at ebp-$10 into floating point register ST0.

The bytes at memory address [ebp-$10] are:

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

The call succeeds, and the floating point register the contains the appropriate value:

enter image description here

Next is the actual call to the RTL Trunc function:

call @TRUNC

Next is the guts of Delphi RTL’s Trunc function:

@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

Or i suppose i could have just pasted it from the rtl, rather than transcribing it from the CPU window:

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;

The exception happens during the actual fistp operation.

fistp qword ptr [esp+$04]

At the moment of this call, the ST0 register will contains the same floating point value:

enter image description here

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.

Leading up to it:

  • sub esp,$0c: I watch it push the the stack down by 12 bytes
  • fstcw word ptr [esp]: i watch it push $027F into the the current stack pointer
  • fldcw word ptr [cwChop]: i watch the floating point control flags change
  • fistp qword ptr [esp+$04]: and it’s about to write the Int64 into the room it made on the stack

and then it crashes.

What can actually be going on here?

It happens with other values as well, it’s not like there’s something wrong with this particular floating point value. But i even tried to setup the test-case elsewhere.

Knowing that the 8-byte hex value of the float is: $41D5EA5E6702098C, i tried to contrive the setup:

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

Which gives:

nsOverlay := $41d62866a2f270dc;

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

v := Trunc(ns)

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

And at the point of the call to @trunc, the floating point register ST0 contains a value:

enter image description here

But the call does not fail. It only fails, every time in this one section of my code.

What could be possibly happening that is causing the CPU to throw an invalid floating point exception?

What is the value of cwChop before it loads the control word?

The value of cwChop looks to be correct before the load control word, $1F32. But after the load, the actual control word is wrong:

enter image description here

Bonus Chatter

The actual function that is failing is something to convert high-performance tick counts into nanoseconds:

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;

I created all the intermeidate temporary variables to try to track down where the failure is.

I even tried to use that as a template to try to reproduce it:

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);

But it works fine. There’s something about when it’s called during a DUnit unit test.

Floating Point control word flags

Delphi’s standard control word: $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)

The crChop control word: $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 

The CTRL flags after loading $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 

All the CPU is doing is turning on a reserved, unused, mask bit.

RaiseLastFloatingPointError()

If you’re going to develop programs for Windows, you really need to accept the fact that floating point exceptions should be masked by the CPU, meaning you have to watch for them yourself. Like Win32Check or RaiseLastWin32Error, we’d like a RaiseLastFPError. The best i can come up with is:

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;

A reproducible case!

I found a case, when Delphi’s default floating point control word, that was the cause of an invalid floating point exception (although I never saw it before now because it was masked). Now that i’m seeing it, why is it happening! And it’s reproducible:

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;

You can see that the ST0 register contains a valid floating point value. The floating point control word is $1372. There floating point exception flag are all clear:

enter image description here

And then, as soon as it executes, it’s an invalid operation:

enter image description here

  • IE (Invalid operation) flag is set
  • ES (Exception) flag is set

I was tempted to ask this as another question, but it would be the exact same question — except this time calling Round().

Цитата
Сообщение от Puporev
Посмотреть сообщение

Код Delphi
1
2
b1:=Sqrt(DfX1); нахожу квадратный корень
b2:=Sqrt(DfX2);

Здесь при определенных значениях х, под корнем получается отрицательное число.
Если числа вводятся безконтрольно, то в этом случае можно выдавать предупреждение
Код Delphi
1
2
if DfX1<0 then showmessage(‘Подкоренное выражение меньше ноля’) else b1:=Sqrt(DfX1);

Да вы правы числа вводятся безконтрольно, вот вся программа

Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
function Func(x:real):real;
begin
Func:=-2*x+0.2*x*x;
end;
function Func1(x:real):real;
begin
Func1:=-2.4*x+0.2*x*x;
end;
function Df1(x:real):real;
begin
Df1:=-2+(0.4*x);
end;
function Df2(x:real):real;
begin
Df2:=-2.4+(0.4*x);
end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
X1,X2:Real;
Fvek,Fvek2:Real;
L:Real;
B1,B2,B3:real;
DfX2,DfX1:Real;
Eps:Real;
A1,A2,x:real;
Sum:Real;
begin
L:=4;
X1:=StrToFloat(Edit1.Text);
X2:=StrToFloat(Edit2.Text);
Eps:=StrToFloat(Edit3.Text);
Fvek:=Func(X1);
Fvek2:=Func1(X2);
A1:=Fvek+Fvek2;
 repeat
  begin
   repeat
   A1:=Fvek+Fvek2;
    DfX1:=Df1(X1);
    DfX2:=Df2(X2);
    X1:=X1-L*DfX1;
    X2:=X2-L*DfX2;
    Fvek:=Func(X1);
    Fvek2:=Func1(X2);
   A2:= Fvek+Fvek2;
  until A1 > A2;
   DfX1:=Df1(X1);
   DfX2:=Df2(X2);
   b1:=Sqrt(DfX1);
   b2:=Sqrt(DfX2);
   sum:=b1+b2;
 end;
  until sum > Eps ;
 
//Label1.Caption:=FloatToStr();
end;
end.

т.е мне ненужно выводить сообщение о том что число под корнем отрицательное, оно должно считаться и сравниваться с Эпсилонтом в DfX2 значение все равно отрицательное и как его посчитать не знаю.
А решаю задачу многомерной оптимизации состоящую из 7 шагов
1- ввожу X1,X2;
2- нахожу их значения в функции F(X1,X2) у меня

Delphi
1
2
Fvek:=Func(X1);
Fvek2:=Func1(X2);

A1:=Fvek+Fvek2; получаю значение функции
3-нахожу производную для X1,X2

Delphi
1
2
DfX1:=Df1(X1);
DfX2:=Df2(X2);

4-нахожу новый X1,X2 подставл производную в формулу
X1:=X1-L*DfX1;
X2:=X2-L*DfX2;
5-нахожу их значения в функции F(X1,X2) уже с новыми X1,X2

Delphi
1
2
3
Fvek:=Func(X1);
 Fvek2:=Func1(X2);
 A2:= Fvek+Fvek2;

6-проверяю A1 и A2 если A1 >A2 тогда идем дальше если нет то цикл должен выполниться еще раз и уменьшить шаг т.е L:=L/2 это я в коде еще не реализовал
7—нахожу новые производные для X1,X2

Delphi
1
2
DfX1:=Df1(X1);
DfX2:=Df2(X2);

и пытаюсь впихнуть их в корень

Delphi
1
2
3
b1:=Sqrt(DfX1);
 b2:=Sqrt(DfX2);
 sum:=b1+b2;

проверяю условием
sum > Eps то выполняем еще иначе получаем долгожданный ответ.

2Shtirlic

Строки вида S[i]:=Group_stat[i].Create

мне непонятны. Ладно бы там было S[i]:=TStakeholder.Create;

… К тому же в начале у вас написано setlength(S, length(Group_stat))

, а чему спрашивается равно length(Group_stat)

изначально-то? Ужас.

В общем, разберитесь с конструированием всех этих объектов, это должно помочь. Т.е., возможное объяснение вашей ошибке может заключаться в обращении к несконструированному объекту, точнее говоря, к неинициализированным полям, в которых в определенный момент может лежать мусор, интерпретируемый, ну например, как тот же +inf, скажем…

— Вт июн 14, 2011 20:29:34 —

Другими словами, вся ваша функция create_save

должна быть переписана и заменена на что-то простое, вроде этого:

Код:

SetLength(Group_stat, count-1);
for i:=0 to count-1 do
    Group_stat[i]:=TStakeholder.Create;

— Вт июн 14, 2011 20:30:41 —

Shtirlic писал(а):

В том и дело, что перед ошибкой в Watch U1=0.

Дык, это-то понятно. Надо смотреть на X1

, X2

, X3

.

    msm.ru

    Нравится ресурс?

    Помоги проекту!

    Пожалуйста, выделяйте текст программы тегом [сode=pas] … [/сode]. Для этого используйте кнопку [code=pas] в форме ответа или комбобокс, если нужно вставить код на языке, отличном от Дельфи/Паскаля.


    Следующие вопросы задаются очень часто, подробно разобраны в FAQ и, поэтому, будут безжалостно удаляться:
    1. Преобразовать переменную типа String в тип PChar (PAnsiChar)
    2. Как «свернуть» программу в трей.
    3. Как «скрыться» от Ctrl + Alt + Del (заблокировать их и т.п.)
    4. Как прочитать список файлов, поддиректорий в директории?
    5. Как запустить программу/файл?
    … (продолжение следует) …


    Вопросы, подробно описанные во встроенной справочной системе Delphi, не несут полезной тематической нагрузки, поэтому будут удаляться.
    Запрещается создавать темы с просьбой выполнить какую-то работу за автора темы. Форум является средством общения и общего поиска решения. Вашу работу за Вас никто выполнять не будет.


    Внимание
    Попытки открытия обсуждений реализации вредоносного ПО, включая различные интерпретации спам-ботов, наказывается предупреждением на 30 дней.
    Повторная попытка — 60 дней. Последующие попытки бан.
    Мат в разделе — бан на три месяца…

    >
    Ошибка при выполнении FloatToStr

    • Подписаться на тему
    • Сообщить другу
    • Скачать/распечатать тему



    Сообщ.
    #1

    ,
    07.09.12, 02:32

      Junior

      *

      Рейтинг (т): нет

      Имеем q:extended и s:string. При попытке выполнить s:=FloatToStr(q) вылетает «invalid floating point operation». При этом q имеет значение 0.0275. Ничего не понимаю. Пробовал менять значения, скажем на q:=0.1, ошибка все равно вылетает.


      RusSun



      Сообщ.
      #2

      ,
      07.09.12, 02:54

        Full Member

        ***

        Рейтинг (т): 7

        ExpandedWrap disabled

          procedure TForm1.Button1Click(Sender: TObject);

          var q:extended; s:string;

          begin

          q:=0.0275;

          s:=FloatToStr(q);

          Edit1.text:=s;

          end;

        попробуй помеять точку на запятую ?

        Добавлено 07.09.12, 02:55
        у меня правда и этот вариант работает)


        Sandy



        Сообщ.
        #3

        ,
        07.09.12, 03:42

          Junior

          *

          Рейтинг (т): нет

          Цитата RusSun @ 07.09.12, 02:54

          попробуй помеять точку на запятую ?

          Попробовал DecimalSeparator:=’,’ (и с точкой тоже) — не помогло.


          MBo



          Сообщ.
          #4

          ,
          07.09.12, 04:44

            Не исключено, что ошибка случилась раньше, и в стеке сопроцессора мусор. И стоит проверить перед операцией Get8087CW


            leo



            Сообщ.
            #5

            ,
            07.09.12, 05:52

              Да, видимо где-то раньше «затесалось» деление 0/0 или операция с неинициализированным вещ.числом (или неумелое использование встроенного asm’а для fpu-вычислений)


              Sandy



              Сообщ.
              #6

              ,
              07.09.12, 07:36

                Junior

                *

                Рейтинг (т): нет

                Покопался в SysUtils. Получилась примерно такая цепочка:
                FloatToStr -> SetString -> FloatToText -> FloatToDecimal
                Вот на FloatToDecimal и запинается. С asm’ом у меня не очень, поэтому просто тупо посмотрел на содержимое регистров CPU.

                Цитата MBo @ 07.09.12, 04:44

                И стоит проверить перед операцией Get8087CW

                Спасибо, почитаю хелп только.


                leo



                Сообщ.
                #7

                ,
                07.09.12, 08:00

                  Копаться в FloatToStr бесполезно, т.к. она работает нормально.
                  Но специфика fpu-операций x86 такова, что исключения генерятся не на самой инструкции, вызвавшей исключение, а на следующей за ней команде fpu. Поэтому во всех встроенных дельфийских функциях для работы с вещ.числами всегда вставляется пустая fpu-команда fwait, чтобы ошибка генерилась внутри самой функции, а не где-то после ее вызова. Поэтому возможные invalid operation, связанные с неверными аргументами Sqrt, Ln и т.п. должны возникать на самом вызове этих функций. Но когда ты, например, просто делишь два числа x/y, то может возникнуть ошибка деления 0/0, которая проявится не сразу, а на какой-то другой последующей операции с fpu (в твоем случае на вызове FloatToStr, хотя дело вовсе не вней). Подобный случай тут недавно обсуждался

                  Сообщение отредактировано: leo — 07.09.12, 08:02


                  Sandy



                  Сообщ.
                  #8

                  ,
                  07.09.12, 08:32

                    Junior

                    *

                    Рейтинг (т): нет

                    Цитата leo @ 07.09.12, 08:00

                    Копаться в FloatToStr бесполезно,

                    Да, так и получилось.

                    После прочтения одной статьи вставил Set8087CW(Get8087CW or $0100). На несколько проходов хватало. После чего опять появлялась ошибка.

                    Цитата MBo @ 07.09.12, 04:44

                    Не исключено, что ошибка случилась раньше, и в стеке сопроцессора мусор.

                    Точно. В куске кода, выдранном из старой программы, обнаружилась пара инструкций IF, использующих две объявленных, но не инициализированных переменных.

                    Всем спасибо!


                    Lumen



                    Сообщ.
                    #9

                    ,
                    07.09.12, 10:52

                      Я обычно FormatFloat использую для преобразования числа с плавающей точкой в строку. Заодно сразу и кол-во знаков после запятой указать можно.
                      Типа такого:

                      ExpandedWrap disabled

                        s:=FormatFloat(‘0.00’, q);


                      Dimonka



                      Сообщ.
                      #10

                      ,
                      07.09.12, 13:50

                        Обычно такие вещи случаются при использовании библиотек с MMX-ом. Например Graphics32. В таких библиотеках обычно пишут, как и после чего переключаться снова на FP.


                        antonn



                        Сообщ.
                        #11

                        ,
                        07.09.12, 16:23

                          Dimonka
                          это об?

                          ExpandedWrap disabled

                            asm

                              emms

                            end;

                          так обычно сама библиотека и следит


                          Sandy



                          Сообщ.
                          #12

                          ,
                          08.09.12, 02:15

                            Junior

                            *

                            Рейтинг (т): нет

                            Цитата Lumen @ 07.09.12, 10:52

                            Я обычно FormatFloat использую для преобразования числа с плавающей точкой в строку.

                            Хм… А на что можно использовать необычно? Это не сарказм, вопрос в целях повышения повышаемости.

                            Цитата Lumen @ 07.09.12, 10:52

                            ExpandedWrap disabled

                              s:=FormatFloat(‘0.00’, q);

                            Кажись наоборот, формат указывается после переменной.


                            Filka



                            Сообщ.
                            #13

                            ,
                            08.09.12, 02:44

                              Senior Member

                              ****

                              Рейтинг (т): 144

                              0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)

                              0 пользователей:

                              • Предыдущая тема
                              • Delphi: Общие вопросы
                              • Следующая тема

                              Рейтинг@Mail.ru

                              [ Script execution time: 0,0753 ]   [ 16 queries used ]   [ Generated: 25.05.23, 01:51 GMT ]  

                              Как исправить ошибка в Delphi 7 вида:
                              invalid floating point

                              Project Project1.exe raised exception class ElnvalidOp with message ‘Invalid floating point operation’. Process stopped. Use Step or Run to continue.

                              Например, при выполнении данного кода в Delphi:

                              procedure TForm1.Button1Click(Sender: TObject);
                              var c,a: double;
                              begin
                              a:=-2.1;
                              c:=Sqrt(a);
                              end;
                              end.

                              выходит ошибка.
                              Это связано с выполнением не допустимой операцией извлечения корня из отрицательного числа функцией Sqrt(a). Для того чтобы исправить ошибку, число должно быть положительным.

                              775


                              Понравилась статья? Поделить с друзьями:
                            • Как составить план счастливого дня
                            • Как найти чертежи в soul knight
                            • X rebirth как найти базу сплитов
                            • Как найти очень красивую жену
                            • Как найти обратно пропорциональное значение