FLOAT_OVERFLOW Exception resizing a chart (64bit)

TeeChart VCL for Borland/CodeGear/Embarcadero RAD Studio, Delphi and C++ Builder.
Post Reply
Whookie
Newbie
Newbie
Posts: 25
Joined: Mon Dec 12, 2016 12:00 am

FLOAT_OVERFLOW Exception resizing a chart (64bit)

Post by Whookie » Fri Mar 31, 2017 11:34 am

This time i got a pretty nasty bug in my 64bit Application (and the bug is gone when compiling with 32bit) which has cost me some time to realize its 64bit. I've attached a sample application (TChart_Resize).
You need to comile and run it as 64bit application! Use the mouse, grab the right border of the applications main form and make the windows smaller until it crashes (the image below shows the form just before it):
before_exception.PNG
before_exception.PNG (3.07 KiB) Viewed 16892 times
A little bit further and you get this exception (text is in german but FLOAT_OVERFLOW is the thing happening):
exception_code.PNG
exception_code.PNG (10.05 KiB) Viewed 16891 times
Its very strange because IIncrement is what causes the problem (its value is 4,94065645841247e-324). So I tried to find the place where the error origins from and found TCgartAxis,Draw,DoDrawLabels

.... cont' in next posting (due to attachment limitations)
Attachments
TChart_Resize.7z
(50.86 KiB) Downloaded 848 times

Whookie
Newbie
Newbie
Posts: 25
Joined: Mon Dec 12, 2016 12:00 am

Re: FLOAT_OVERFLOW Exception resizing a chart (64bit)

Post by Whookie » Fri Mar 31, 2017 12:33 pm

... So I tried to find the place where the error origins from and found TCgartAxis,Draw,DoDrawLabels
exception_01.PNG
exception_01.PNG (10.02 KiB) Viewed 16877 times
At the breakpoint (line 7454) IIncrement has the value mentioned in the previous post (and got calculated a few lines above).

This might be the place to patch the the problem (but maybe not to the best place to fix it).
Because IIncrement is a double its not good practice to check it against 0 in this case a

If (CompareValue(IIncrement, 0) = GreaterThanValue) Or

might be working (otherwise IsZero() should be used to check floatingpoint values! Also note that both functions select a proper epsilon when omitted).

But this is just a place to work around that problem, so I tried to dig a bit deeper right down to CalcIncrement, which is called in line 7447. Folloing the code one comes across TChartAxis.CalcXYIncrement and then ends in TChartAxis.CalcLabelsIncrement where - for this case -

Code: Select all

...
else result:=MinAxisIncrement;
is executed (MinAxisIncrement is a constant and has the value 4.94065645841247e-324, hence this is the place that value comes from). So we can go back to the place where the exeption happens and will see the following code:
dbg.PNG
dbg.PNG (53.34 KiB) Viewed 16882 times
The instruction the pc is at this moment (divsd xmm0,qword ptr [rbx+$28] will raise the exception....

Whookie
Newbie
Newbie
Posts: 25
Joined: Mon Dec 12, 2016 12:00 am

Re: FLOAT_OVERFLOW Exception resizing a chart (64bit)

Post by Whookie » Fri Mar 31, 2017 3:36 pm

I have fixed the problem for my current version (v2017.20.170306) by adding the following code:

TeEngine.pas

Code: Select all

Function TChartAxis.CalcIncrement:TAxisValue;
...
Begin
  ...
     else
        result:=result*0.5;

  // Patch: Begin
  if IsZero(result) then
    Result := 0.00000001;
  // Patch: End;
End;
I had to put it in there because there are more location in the code which throw exceptions, when you resize the chart down to 0.
BTW.: I had that problem with previous versions too (at least with 2016.19.161025)

Yeray
Site Admin
Site Admin
Posts: 9612
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: FLOAT_OVERFLOW Exception resizing a chart (64bit)

Post by Yeray » Thu Apr 06, 2017 6:40 am

Hello,

Sounds like this:
http://www.teechart.net/support/viewtop ... =3&t=16390

I've added your fix proposal to the ticket #1745.
Thanks for sharing it.

Feel free to add your mail to the CC list to be automatically notified when an update arrives.
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Whookie
Newbie
Newbie
Posts: 25
Joined: Mon Dec 12, 2016 12:00 am

Re: FLOAT_OVERFLOW Exception resizing a chart (64bit)

Post by Whookie » Mon Apr 10, 2017 1:17 pm

I did some more tests (because I got some more Excpetions) and finally changed my fix to:

Code: Select all

      MinAxisIncrement :TAxisValue = {$IFDEF TEEVALUESINGLE}
                                     1.40129846432482e-45
                                     {$ELSE}
                                       {$IFDEF WIN64}
                                         0.00000001             
                                       {$ELSE}
                                         4.94065645841247e-324  //this IS MinDouble
                                       {$ENDIF}
                                     {$ENDIF}; // Epsilon 0.000000000001;  { <-- "double" for BCB }
this is in the header of TeEngine.pas.

I'm not sure where the problem is with FPU in 64 bit and tryed the following in a FormCreate of an empty Application.:

Code: Select all

Var
  a,b,c: Double;
begin
  a := MinDouble;
  b := 12;
  if b/a > 0 then
  begin

  end;
Then I tried to find a better "MinDouble" but as it seems the divsd command throws exeptions with values smaller than 1.0E-300 (approx.). So I ended up with 0.00000001 as MinAxisIncrement. Don't know if that helps (I could not find any doc on the intel-side which could explain that behaviour).

Whookie
Newbie
Newbie
Posts: 25
Joined: Mon Dec 12, 2016 12:00 am

Re: FLOAT_OVERFLOW Exception resizing a chart (64bit)

Post by Whookie » Wed Apr 12, 2017 12:49 pm

As it seems Intel states (64-ia-32-architectures-software-developer-vol-1-manual):

4.9.1.4 Numeric Overflow Exception (#O)
The processor reports a floating-point numeric overflow exception whenever the rounded result of an instruction
exceeds the largest allowable finite value that will fit into the destination operand. ... overflow occurs when a rounded result falls at or
outside this threshold range.

for Double Precision | x | ≥ 1.0 ∗ 2^1024

Now dividing any number by such a small value easly produces such an overflow (12/1e-324 =1,2e+325 which exceeds maximum double precision of 1.7e+308)

Intel further on states:
When a numeric-overflow exception occurs and the exception is masked, the processor sets the OE flag and
returns one of the values shown in Table 4-10, according to the current rounding mode. See Section 4.8.4,
“Rounding.”

When numeric overflow occurs and the numeric-overflow exception is not masked, the OE flag is set, a software
exception handler is invoked, and the source and destination operands either remain unchanged or a biased result
is stored in the destination operand (depending whether the overflow exception was generated during an
SSE/SSE2/SSE3 floating-point operation or an x87 FPU operation).


So the conclusion is, that delphi runtime (?) uses to mask that exception in 32bit mode but not in 64bit mode.
That problem might be fixed by just changing the SSEExceptionMask()...

I will give this a try and report my discoveries...

Whookie
Newbie
Newbie
Posts: 25
Joined: Mon Dec 12, 2016 12:00 am

Re: FLOAT_OVERFLOW Exception resizing a chart (64bit)

Post by Whookie » Thu Apr 13, 2017 2:23 pm

As promised here are the results of my investigations:

First of all FPU and SSE masks are set by Delphi and they are different for 32bit and 64bit applications. See the following screenshot:
exception.png
exception.png (10.06 KiB) Viewed 16803 times
Both screenshots are from my test application, and as far as I can tell those options are set with every start of an application.
Basically you can see that 32bit apps use FPU instructions while 64bit apps use SSE.

NOTE: If a checkbox is set, it means that this Exception is masked out (not thrown).

With that knowledge in mind we can get back to the problem with the (any) division. In 32bit mode the

Code: Select all

  Begin
    if Abs(IRange/IIncrement)<10000 then  { if less than 10000 labels... }
division is executed as:

Code: Select all

       fdiv qword ptr [edx-$20]
If we use IRange = 18 and IIncrement = MinDouble then the operation sets the DE flag (Denormalization exception) - but as you can see above - that exception is masked out and thus no exception is thrown. Further on, no other things seem to happen with the result of that operation.

18/MinDouble = 8,50705917302346e+37

Where the correct result would be 1,1002752289599262531830383881606e+226

So we get a rather big number but far less then the expected result.

In 64bit mode on the other hand, SSE is used and here exOverflow is enabled (no check set). The division is made by:

Code: Select all

divsd xmm0,qword ptr [rbx+$28]
and immeaditly raises the exception. No (other) errorflags could be observed, but thats most certainly the result of the exception handler.


After that said, we can try and change the SSE exception mask:
IgnorOv.png
IgnorOv.png (13.84 KiB) Viewed 16804 times
and as you can see, the chart can be resized down to nothing without any exception!

I think, this is a acceptable solution (as programms in 64bit (hopefully) behave the same as in 32bit).

In code this can be done with something like this:

Code: Select all

  LEM := GetSSEExceptionMask;
  if Not (exOverflow in LEM) then
  begin
    SetSSEExceptionMask(LEM+[exOverflow]);
  end;
The only question remaining is, who should be responsible for this. In my opinion it would be nice if you set this mask (nobody can stumble into that pit again this way).

Last but not least, I have attached my sample application but be warned playing around with all the flags might lead to other unexpected exceptions!
Attachments
TChart_Resize.7z
(53.45 KiB) Downloaded 817 times

Yeray
Site Admin
Site Admin
Posts: 9612
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: FLOAT_OVERFLOW Exception resizing a chart (64bit)

Post by Yeray » Wed Apr 19, 2017 7:40 am

Hello,

Thanks for sharing your investigation!

We also found that possible solution about disabling exceptions but we think it would cause more problems than it solves.
If we activated/deactivated the exception every time we need it, there would be a performance penalization in return (when having millions of points), and other threads modifying the same flags could cause more problems.

We think it isn't worth implementing that solution at that cost. It's better if the developer prevents this situation when adding points or before drawing the chart the first time.
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Post Reply