devxlogo

Dealing with Floating-point Exceptions in MSVC78

Dealing with Floating-point Exceptions in MSVC78

ecause the scientific and engineering numeric floating-point calculation approximates real arithmetic, the precision of this format can create situations wherein your floating-point operations results are not acceptable in some domains. A programming error can create some pretty sticky exceptional situations, but this is also a byproduct of the nature of the domain itself. You can find a numerous discussions on different software implementation techniques for dealing with arithmetic floating-point exceptions. Some solutions are slower, others are faster. Some of them prevent occurrences of exceptions, other respond to exceptions.

This article is the result of a study on how to take advantage of what is available in Microsoft’s implementation of CC++ in VS2003 and VS2005 for the latest x86-based platform that supports IEEE Standard 754 for Binary Floating-Point Arithmetic.

You’ll learn how to:

  • Enable the floating-point exceptions of your interest.
  • Trap them.
  • Set the Visual Studio compiler options.

Enabling the Floating-point Exceptions
By default, floating-point exceptions are not enabled. The following code snippet shows how to enable the x86 (x87 FPU) exceptions that you want to trap:

//Set the x86 floating-point control word according to what//exceptions you want to trap. _clearfp(); //Always call _clearfp before setting the control            //word//Because the second parameter in the following call is 0, it//only returns the floating-point control wordunsigned int cw = _controlfp(0, 0); //Get the default control                                    //word//Set the exception masks off for exceptions that you want to//trap.  When a mask bit is set, the corresponding floating-point//exception is //blocked from being generating.cw &=~(EM_OVERFLOW|EM_UNDERFLOW|EM_ZERODIVIDE|       EM_DENORMAL|EM_INVALID);//For any bit in the second parameter (mask) that is 1, the //corresponding bit in the first parameter is used to update//the control word.  unsigned int cwOriginal = _controlfp(cw, MCW_EM); //Set it.                            //MCW_EM is defined in float.h.                            //Restore the original value when done:                            //_controlfp(cwOriginal, MCW_EM);

For example, an exception corresponding to EM_INVALID can be thrown in the case of 0.0/0.0. An exception corresponding to EM_OVERFLOW can be thrown when a variable of the double type is assigned to a variable of the float type and its value is larger in than the largest finite number that can be represented by the float type format. This is demonstrated in the following code:

double d = 1.34E222;float f = d;

This next code shows you how to enable the SIMD floating-point exceptions that you want to trap:

unsigned long cntrReg;_asm{     stmxcsr [cntrReg]        //Get MXCSR register     and [cntrReg], 0FFFFFF7Fh//bit 7 - invalid instruction mask                              //bit 9 ? divide-by-zero mask                              //bit 10 - overflow mask                              //bit 11 ? underflow mask	      ldmxcsr [cntrReg]        //Load MXCSR register}

After you’ve enabled the exceptions, you may catch them.

Catching the Floating-point Exceptions
The try-except statement is a Microsoft extension to the C and C++ languages that enables 32-bit target applications to gain control when exceptions occur. These exceptions call C, or structured or Win32 exceptions and are identified by an unsigned integer value. Also, they are referenced as “asynchronous.” This means that they are secondary to the normal flow of control. According to Intel documentation, in the native mode (not MS DOS), the software exception handler is invoked immediately before the execution of the next floating-point instruction?unless it is not one of the non-waiting instructions, the WAITFWAIT instruction, or the next MMX? instruction. The following code snippet shows how to use C exception handling mechanism:

__try  //The start of the guarded block.{      //Code that may generate a floating-point exception}    //The end of the guarded block.__except (EvaluateException(GetExceptionCode())){     //May evaluate globalVar here      //Code that does appropriate processing}//May evaluate globalVar here 

When an exception occurs during the execution of the guarded block or any function called from it, the _except expression is evaluated. Here, GetExceptionCode() retrieves the code that identifies the type of occurred exception. The EvaluateException() calls the filter expression and can be any function that returns an integer value. There are three predefined values that can be returned and that define the program execution pass. The values are defined in the excpt.h file, and they are:

  • EXCEPTION_EXECUTE_HANDLER: The except block is executed.
  • EXCEPTION_CONTINUE_SEARCH: The exception handler search continues.
  • EXCEPTION_CONTINUE_EXECUTION: Execution continues as the exception never occurred.

The following code shows an example of the filter expression for x86:

int EvaluateException(int exceptionCode){     //May evaluate exeptionCode     //May set a global variable that can be evaluated in     //or after _except guard code.     globalVar =  _clear87(); //Get floating-point status word.                              //The above function also sets the                              //busy bit to 0 to show that FPU                              //is not busy.     return EXCEPTION_EXECUTE_HANDLER;//force execution of                                      //the _except() guard code.  }

Instead of calling EvaluateException(), you may call _fpieee_flt(). This function is defined in the fpieee.h file:

	_fpieee_flt(GetExceptionCode(), GetExceptionInformation(),      MyFpieeeHandler)You supply MyFpieeeHandler() according to its signature:	int MyFpieeeHandler( _FPIEEE_RECORD* );

The possible return values are the same as for EvaluateException().

The following code shows an example of the filter expression for x86:

int EvaluateException(int exceptionCode){     unsigned long cntrReg = 0;     _asm     {          stmxcsr [cntrReg]         //Get MXCSR register          and [cntrReg], 000000001h //Get invalid operation                                    //flag bit 0.                                    //bit 2 ? divide-by-zero flag                                    //bit 3 - overflow flag                                    //bit 4 ? underflow flag     }     if (cntReg > 0)     {          _asm          {               stmxcsr [cntrReg]         //Get MXCSR register               and [cntrReg], 0FFFFFFFEh //Clear sticky                                          //invalid operation flag               ldmxcsr [cntrReg]         //Load MXCSR register          }          globalVar = _SW_INVALID;     }     return EXCEPTION_EXECUTE_HANDLER;//force execution of                                      //the _except() guard code.  }

If a C exception is raised in a C++ program, it can be handled as described above. However, for C++, Microsoft recommends that you use the C++ try-catch exception handling mechanism. This mechanism is fully “synchronous,” meaning that it only occurs when it is thrown. The difference between C structured and C++ typed exception handling is that the latter requires the use of the ellipsis handler in order to catch the floating-point exceptions in C++ code. The following code demonstrates this:

try{     //Code that may generate a floating-point exception}catch(. . .){     int fpStatusWord =  _clear87();     //You may analyze the bits of the status word.     //Code that does appropriate processing}
Author’s Note: Use catch(. . .) with precaution; see the compilers option /EH below.

It’s also possible to associate the Win32 exceptions with specific wrapper classes, as shown below. Here’s how to define a wrapper:

class SeWrapper{      private:          unsigned int seCode;          SeWrapper() {}     public:     SeWrapper(unsigned int n) : (seCode(n) {}     ~SeWrapper() {}     unsigned int GetSeCode() {return seCode;}};

Define a native compiled function (compiled with /c) according to the following signature:

void SeTranslator(unsigned int n, struct _EXCEPTION_POINTER* ptr){     //This function should throw an exception of SeWrapper class.     throw SeWrapper(n);}

Install the translator function that will be called by the internal exception-handling mechanism when a C exception is thrown:

_set_se_translator(SeTranslator);

Each thread should install its own translator function. Now, you can use C++ typed exception:

try{     //Code that may generate a floating-point exception}catch(SeWrapper ex){     //You may analyze ex.     //Code that does appropriate processing}

After you have your code in place, you need to select right project properties.

The Visual Studio Compiler Options
For both Debug and Release configurations, Visual Studio offers the “Code Generation” property page in “C/C++” property folder. Here, you can select the desired option for the “Enable Enhanced Instruction Set” property. The available options are:

  1. “Not Set”
  2. “Streaming SIMD Extensions (/arch:SSE):” The new set of instructions and registers on the Pentium III processor allows Single-Instruction Multiple-Data (SIMD) computations to be made on single-precision floating-point numbers. You may see the assembler code for both options above in a disassembly window during debugging that looks like:
        c = a/b;  //inside a __try block         100114A8  fld         qword ptr [a]          100114AB  fdiv        qword ptr [b]          100114AE  fstp        qword ptr [c]          100114B1  wait                 }    100114B2  wait                 100114B3  mov         dword ptr [ebp-4],0FFFFFFFEh     100114BA  jmp         $LN6+12h (100114EBh)     _except(EvaluateException(GetExceptionCode()))

    Because the assembly code is the same for both options, no changes are needed for the source code in order to trap floating-point exceptions when the option is set to /arch:SSE.

  3. “Streaming SIMD Extensions 2 (/arch:SSE2)”: The new set of instructions on the Pentium 4 and Intel Xeon processors operates on the XXM and MXCSR registers and performs SIMD computations on double-precision floating-point and integer numbers. The assembler code for this option looks like:
        c = a/b;  //inside a __try block          1001325A  movsd       xmm0,mmword ptr [a]          1001325F  divsd       xmm0,mmword ptr [b]          10013264  movsd       mmword ptr [c],xmm0          10013269  wait                      1001326A  mov         dword ptr [ebp-4],0FFFFFFFFh          10013271  jmp         $L18835+12h (100132A2h)     }    _except(EvaluateException(GetExceptionCode()))

The SSE and SSE2 instructions can generate the same floating-point exceptions (mentioned above) that are generated with x87 floating-point instructions and are defined in IEEE Standard 754. There is also a Denormal operand exception that is not defined in the standard. Each of the exception conditions has a corresponding flag and mask in the MXCSR register. Control register CR4’s OSXMMEXCEPT flag provides additional control over generation of SIMD floating-point exceptions.

Comparing VS 2005 with VS 2003
VS 2005 introduced some improvements in dealing with floating-point exceptions. To compare with VS 2003:

  1. The options for the Code Generation Enable C++ Exceptions property in VS2003 are:
    • No: Structured and C++ exceptions are captured, but C++ objects that go out ?f-scope as a result of exception will not be destroyed. This is an equivalent to /EHsc-.
  2. Yes (/EHsc): Only C++ exceptions will be captured. This means that catch(. . .) will not catch C exceptions. The C++ objects that should be destroyed will be destroyed. During optimization, the Release configuration compiler may optimize away the catch clause if it does not see a throw nside the try block.
  3. Changes for VS2005:
    • Yes (/EHsc): If a C exception is generated, the C++ objects that should be destroyed will not be destroyed. This means that C++ exception handling works correctly only for C++ exceptions; in other words, catch (. . .) does not catch C exceptions.
    • Yes with SHE Exceptions (/EHa) catch (. . .) will catch C and C++ exceptions. Also, if you install a translator function, this option should be selected.
  4. The options for Code GenerationEnable Floating Point Exceptions property (Enable Floating Point Exception Semantics) in VS 2005 are:
    • No: Turned off.
    • Yes (/fp:except): The compiler introduces a WAIT instruction after each floating-point instruction. This forces the processor to synchronize with the state of the FPU and handle any pending exceptions. Also, this prevents the compiler from illegally optimizing floating-point code.
    • In addition to this option, which is used on a file-by-file basis, you can control the same floating-point semantic on a function-by-function basis by using the float_control pragma directive. For example:
          #pragma   float_control (except, off | on)
    • This option is not compatible with /arch:SSE.
    • The options for ?OptimizationFloating-Point Consistency? property in VS2003 are:
    • Default Consistency: The compiler uses processor’s 80 bit registers to hold the intermediate results of floating-point calculations.
    • Improved Consistency (/Op): The compiler loads data from memory prior to each floating-point operation (optimization is disabled).
  5. Changes for VS2005:
    • This property has been removed. The new property is Code GenerationFloating Point Model with these options:
      • Precise (fp:precise): Replaces the /Ops option.
      • Strict (/fp:strict): Creates the strictest code.
      • Fast (/fp:fast): The compiler creates the fastest code in the majority of cases.
      • If you use _controlfp() in code compiled with VS 2005, you will get a C4996 warning that this function has been deprecated. Use _controlfp_s() instead.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist