FLOAT_OVERFLOW Exception resizing a chart (64bit)
FLOAT_OVERFLOW Exception resizing a chart (64bit)
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): A little bit further and you get this exception (text is in german but FLOAT_OVERFLOW is the thing happening): 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)
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): A little bit further and you get this exception (text is in german but FLOAT_OVERFLOW is the thing happening): 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 863 times
Re: FLOAT_OVERFLOW Exception resizing a chart (64bit)
... So I tried to find the place where the error origins from and found TCgartAxis,Draw,DoDrawLabels
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 -
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:
The instruction the pc is at this moment (divsd xmm0,qword ptr [rbx+$28] will raise the exception....
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;
The instruction the pc is at this moment (divsd xmm0,qword ptr [rbx+$28] will raise the exception....
Re: FLOAT_OVERFLOW Exception resizing a chart (64bit)
I have fixed the problem for my current version (v2017.20.170306) by adding the following code:
TeEngine.pas
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)
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;
BTW.: I had that problem with previous versions too (at least with 2016.19.161025)
Re: FLOAT_OVERFLOW Exception resizing a chart (64bit)
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.
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,
Yeray Alonso Development & Support Steema Software Av. Montilivi 33, 17003 Girona, Catalonia (SP) | |
Please read our Bug Fixing Policy |
Re: FLOAT_OVERFLOW Exception resizing a chart (64bit)
I did some more tests (because I got some more Excpetions) and finally changed my fix to:
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.:
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).
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 }
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;
Re: FLOAT_OVERFLOW Exception resizing a chart (64bit)
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...
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...
Re: FLOAT_OVERFLOW Exception resizing a chart (64bit)
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: 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
division is executed as:
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:
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:
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:
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!
First of all FPU and SSE masks are set by Delphi and they are different for 32bit and 64bit applications. See the following screenshot: 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... }
Code: Select all
fdiv qword ptr [edx-$20]
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]
After that said, we can try and change the SSE exception mask:
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;
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 831 times
Re: FLOAT_OVERFLOW Exception resizing a chart (64bit)
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.
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,
Yeray Alonso Development & Support Steema Software Av. Montilivi 33, 17003 Girona, Catalonia (SP) | |
Please read our Bug Fixing Policy |