FPU Control & Exception Handling

 

See Also:  Error Handling

 

The FPU -- Floating Point Unit -- has configurable settings that allow you to control the precision of calculations, the rounding mode, and exception handling.  These settings are defined as part of the IEEE 754 standard, and are implemented in today's computers at the hardware level by various PC chipmakers.  This help file topic is mainly concerned with explaining how to implement these settings from uCalc Fast Math Parser, and not so much with why or when to use them.  For a detailed explanation of the FPU, see the IA-32 Intel Architecture Software Developer’s Manual, which is available at www.intel.com.  Also consult documentation for the IEEE 754 standard.  Visit www.ieee.org to find out more.

 

uCalc Fast Math Parser keeps two separate FPU control word settings, which are insulated one from the other.  One is for uCalc Fast Math Parser, and the other is for your host program.  If you have another DLL component that uses a different FPU setting, this will not affect uCalc Fast Math Parser or vice-versa.  Things run most efficiently if uCalc's FPU word is the same as that of your host program.

 

If you mask a particular exception, then instead of raising an error when this exception is encountered, a special value will be returned.  For instance, if the division by 0 exception is masked (with uc_FPU_Mask_ZeroDivide), then 1/0 would return Inf (for Infinity) instead of raising a Divison by 0 error.

 

Note: Managed code under Visual Studio 2012 does not work well with unmasked settings.  Therefore, it is best to leave the FPU control word with the default settings in C#, VC++, and VB.NET.

 

Here is the list of uCalc FPU commands:

 

uc_SetFPU

uc_SetFPU_HostProg

uc_GetFPU

uc_GetFPU_HostProg

uc_ToggleFPU

uc_ToggleFPU_HostProg

 

Here is the list of FPU Control word settings:

 

uc_FPU_Mask_InvalidOp

uc_FPU_Mask_DenormalOp

uc_FPU_Mask_ZeroDivide

uc_FPU_Mask_Overflow

uc_FPU_Mask_Underflow

uc_FPU_Mask_PrecisionLoss

 

uc_FPU_Precision_Single

uc_FPU_Precision_Double

uc_FPU_Precision_Extended

 

uc_FPU_Round_Even

uc_FPU_Round_Down

uc_FPU_Round_Up

uc_FPU_Round_Toward

 

If you wanted to toggle the Division by 0 bit for instance, you'd do it like this (in VB):

 

ucFPU(uc_ToggleFPU, uc_FPU_Mask_ZeroDivide)

 

where the first argument is a command, the second argument is always 0, and the third argument is the particular setting you want to toggle.  If the ZeroDivide mask was off, uc_ToggleFPU would turn it on.  If it was on, uc_ToggleFPU would turn it off.  If you wanted to set multiple bits instead of toggling just one, then you would add them together, like this (using uc_SetFPU instead of uc_ToggleFPU):

 

ucFPU(ucSetFPU, uc_FPU_Mask_PrecisionLoss + uc_FPU_Precision_Extended + uc_FPU_Round_Even)

 

The default start-up FPU setting is arbitrarily configured as follows:

 

uc_FPU_Mask_InvalidOp

+ uc_FPU_Mask_DenormalOp

+ uc_FPU_Mask_ZeroDivide

+ uc_FPU_Mask_Overflow

+ uc_FPU_Mask_Underflow

+ uc_FPU_Mask_PrecisionLoss

+ uc_FPU_Precision_Extended

 

The following three Visual Basic examples assume that you are starting with the default setting.  If you have a different setting, you can start each example with the following line:

 

ucFPU(ucSetFPU, uc_FPU_Mask_InvalidOp _

+ uc_FPU_Mask_DenormalOp _

+ uc_FPU_Mask_ZeroDivide + uc_FPU_Mask_Overflow _

+ uc_FPU_Mask_Underflow + uc_FPU_Mask_PrecisionLoss _

+ uc_FPU_Precision_Extended)

 

The demo files for the various supported compilers contain source code examples for toggling the FPU in those programming languages.

 

Example 1:  Division by 0 vs Inf

 

Visual Basic

Print ucEvalStr("1/0") ' This returns Inf

ucFPU(uc_ToggleFPU, uc_FPU_Mask_ZeroDivide)

Print ucEvalStr("1/0") ' This returns a Division by 0 error

ucFPU(uc_ToggleFPU, uc_FPU_Mask_ZeroDivide)

Print ucEvalStr("1/0") ' This returns Inf again

 

 

Example 2:  Overflow vs Inf

 

Visual Basic

Print ucEvalStr("100!^150") ' This returns Inf

ucFPU(uc_ToggleFPU, uc_FPU_Mask_Overflow)

Print ucEvalStr("100!^150") ' This returns a Floating point overflow error

ucFPU(uc_ToggleFPU, uc_FPU_Mask_Overflow)

Print ucEvalStr("100!^150") ' This returns Inf again

 

 

Example 3:  Invalid Op vs NaN

 

Visual Basic

Print ucEvalStr("Sqr(-5)") ' This returns NaN

ucFPU(uc_ToggleFPU, uc_FPU_Mask_InvalidOp)

Print ucEvalStr("Sqr(-5)") ' This returns an Invalid operation error

ucFPU(uc_ToggleFPU, uc_FPU_Mask_InvalidOp)

Print ucEvalStr("Sqr(-5)") ' This returns NaN again

 

 

Example 4:  Allowing your compiler to catch an exception

 

In this example, a uCalc error handler is defined, which returns ucReRaise in order to allow the host Delphi compiler to catch the exception, using try / except.  If you enter an expression such as 1/0, and the Division by 0 FPU setting is not masked, then a message box will display:  This is handled by Try/Except in Delphi.

 

Delphi

function MyErrorHandlerB(t: Longword): Longword; stdcall;

begin

   // Additional code may go here if necessary

   Result := ucReRaise;

end;

 

procedure TForm1.btnEvalClick(Sender: TObject);

begin

   try

      txtResult.Text := ucEvalStr(txtExpression.Text);

   except

      ShowMessage('This is handled by Try/Except in Delphi');

   end;

end;

 

// The following line can go in TForm1.FormCreate()

uCalc(uc_AddErrorHandler, 0, Longword(@MyErrorHandlerB));

 

 

 

What’s different in version 3.0

·         Instead of setting the FPU using the general uCalc() function, you should now use the provided ucFPU() function.  Also, it now takes two arguments instead of three (the second argument was unused).