Exceptional Floating Point

Original Author: Bruce-Dawson

Floating-point math has an answer for everything, but sometimes that’s not what you want. Sometimes instead of getting an answer to the question sqrt(-1.0) (it’s NaN) it’s better to know that your software is asking imaginary questions.

The IEEE standard for floating-point math defines five exceptions that shall be signaled when certain conditions are detected. Normally the flags for these exceptions are raised (set), a default result is delivered, and execution continues. This default behavior is often desirable, especially in a shipping game, but during development it can be useful to halt when an exception is signaled.

Halting on exceptions can be like adding an assert to every floating-point operation in your program, and can therefore be a great way to improve code reliability, and find mysterious behavior at its root cause.

This article is part of a series on floating-point. The complete list of articles in the series is:

Let’s get it started again

Invalid operation: this is signaled if there is no usefully definable result, such as zero divided by zero, infinity minus infinity, or sqrt(-1). The default result is a NaN (Not a Number)

Division by zero: this is signaled when dividing a non-zero number by zero. The result is a correctly signed infinity.

Overflow: this is signaled when the rounded result won’t fit. The default result is a correctly signed infinity.

Underflow: this is signaled when the result is non-zero and between -FLT_MIN and FLT_MIN. The default result is the rounded result.

Inexact: this is signaled any time the result of an operation is not exact. The default result is the rounded result.

The underflow exception is usually not of interest to game developers – it happens rarely, and usually doesn’t detect anything of interest. The inexact result is also usually not of interest to game developers – it happens frequently (although not always, and it can be useful to understand what operations are exact) and usually doesn’t detect anything of interest.

That leaves invalid operation, division by zero, and overflow. In the context of game development these are usually truly exceptional. They are rarely done intentionally, so they usually indicate a bug. In many cases these bugs are benign, but occasionally these bugs indicate real problems. From now one I’ll refer to these first three exceptions as being the ‘bad’ exceptions and assume that game developers would like to avoid them, if only so that the exceptions can be enabled without causing crashes during normal game play.

When can divide by zero be useful?

While the ‘bad’ exceptions typically represent invalid operations in the context of games, this is not necessarily true in all contexts. The default result (infinity) of division by zero can allow a calculation to continue and produce a valid result, and the default result (NaN) of invalid operation can sometimes allow a fast algorithm to be used and, if a NaN result is produced, a slower and more robust algorithm to be used instead.

The classic example of the value of the division by zero behavior is calculation of parallel resistance. The formula for this for two resistors with resistance R1 and R2 is:

Because division by zero gives a result of infinity, and because infinity plus another number gives infinity, and because a finite number divided by infinity gives zero, this calculation calculates the correct parallel resistance of zero when either R1 or R2 is zero. Without this behavior the code would need to check for both R1 and R2 being zero and handle that case specially.

In addition, this calculation will give a result of zero if R1 or R2 are very small – smaller than the reciprocal of FLT_MAX or DBL_MAX. This zero result is not technically correct. If a programmer needs to distinguish between these scenarios then monitoring of the overflow and division by zero flags will be needed.

The interpretation of divide-by-zero as infinity bothers some as can be seen in this official interpretation request/response, which explains the decision quite well.

Resistance is futile

Assuming that we are not trying to make use of the divide-by-zero behavior we need a convenient way of turning on the ‘bad’ floating-point exceptions. And, since we have to coexist with other code (calling out to physics libraries, D3D, and other code that may not be ‘exception clean’) we also need a way of temporarily turning off all floating-point exceptions.

The appropriate way to do this is with a pair of classes whose constructors and destructors do the necessary magic. Here are some classes that do that, for VC++:

// Declare an object of this type in a scope in order to suppress
// all floating-point exceptions temporarily. The old exception
// state will be reset at the end.
class FPExceptionDisabler
{
public:
FPExceptionDisabler()
{
// Retrieve the current state of the exception flags. This
// must be done before changing them. _MCW_EM is a bit
// mask representing all available exception masks.
_controlfp_s(&mOldValues, _MCW_EM, _MCW_EM);
// Set all of the exception flags, which suppresses FP
// exceptions on the x87 and SSE units.
_controlfp_s(0, _MCW_EM, _MCW_EM);
}
~FPExceptionDisabler()
{
// Clear any pending FP exceptions. This must be done
// prior to enabling FP exceptions since otherwise there
// may be a 'deferred crash' as soon the exceptions are
// enabled.
_clearfp();
// Reset (possibly enabling) the exception status.
_controlfp_s(0, mOldValues, _MCW_EM);
}
private:
unsigned int mOldValues;
// Make the copy constructor and assignment operator private
// and unimplemented to prohibit copying.
FPExceptionDisabler(const FPExceptionDisabler&);
FPExceptionDisabler& operator=(const FPExceptionDisabler&);
};
// Declare an object of this type in a scope in order to enable a
// specified set of floating-point exceptions temporarily. The old
// exception state will be reset at the end.
// This class can be nested.
class FPExceptionEnabler
{
public:
// Overflow, divide-by-zero, and invalid-operation are the FP
// exceptions most frequently associated with bugs.
FPExceptionEnabler(unsigned int enableBits = _EM_OVERFLOW |
_EM_ZERODIVIDE | _EM_INVALID)
{
// Retrieve the current state of the exception flags. This
// must be done before changing them. _MCW_EM is a bit
// mask representing all available exception masks.
_controlfp_s(&mOldValues, _MCW_EM, _MCW_EM);
// Make sure no non-exception flags have been specified,
// to avoid accidental changing of rounding modes, etc.
enableBits &= _MCW_EM;
// Clear any pending FP exceptions. This must be done
// prior to enabling FP exceptions since otherwise there
// may be a 'deferred crash' as soon the exceptions are
// enabled.
_clearfp();
// Zero out the specified bits, leaving other bits alone.
_controlfp_s(0, ~enableBits, enableBits);
}
~FPExceptionEnabler()
{
// Reset the exception state.
_controlfp_s(0, mOldValues, _MCW_EM);
}
private:
unsigned int mOldValues;
// Make the copy constructor and assignment operator private
// and unimplemented to prohibit copying.
FPExceptionEnabler(const FPExceptionEnabler&);
FPExceptionEnabler& operator=(const FPExceptionEnabler&);
};

The comments explain a lot of the details, but I’ll mention a few here as well.

_controlfp_s is the secure version of the portable version of the old _control87 function. _controlfp_s controls exception settings for both the x87 and SSE FPUs. It can also be used to control rounding directions on both FPUs, and on the x87 FPU it can be used to control the precision settings. These classes use the mask parameter to ensure that only the exception settings are altered.

The floating-point exception flags are sticky, so when an exception flag is raised it will stay set until explicitly cleared. This means that if you choose not to enable floating-point exceptions you can still detect whether any have happened. And – not so obviously – if the exception associated with a flag is enabled after the flag is raised then an exception will be triggered on the next FPU instruction, even if that is several weeks after the operation that raised the flag. Therefore it is critical that the exception flags be cleared each time before exceptions are enabled.

Typical usage

The floating-point exception flags are part of the processor state which means that they are per-thread settings. Therefore, if you want exceptions enabled everywhere you need to do it in each thread, typically in main/WinMain and in your thread start function, by dropping an FPExceptionEnabler object in the top of these functions.

When calling out to D3D or any code that may use floating-point in a way that triggers these exceptions you need to drop in an FPExceptionDisabler object.

Alternately, if most your code is not FP exception clean then it may make more sense to leave FP exceptions disabled most of the time and then enable them in particular areas, such as particle systems.

Because there is some cost associated with changing the exception state (the FPU pipelines will be flushed at the very least) and because making your code more crashy is probably not what you want for your shipping game you should put #ifdefs in the constructors and destructors so that these objects become NOPs in your retail builds.

There have been various instances in the past (printer drivers from a manufacturer who shall not be named) that would enable floating-point exceptions and leave them enabled, meaning that some perfectly legitimate software would start crashing after calling into third-party code (such as after printing). Having somebody’s hapless code crash after calling a function in your code is a horrible experience, so be particularly careful if your code may end up injected into other processes. In that situation you definitely need to not leave floating-point exceptions enabled when you return, and you may need to be tolerant of being called with floating-point exceptions enabled.

Performance implications of exceptions

Raising the exception flags (triggering a floating-point exception) should have no performance implications. These flags are raised frequently enough that any CPU designer will make sure that doing so is free. For example, the inexact flag is raised on virtually every floating-point instruction.

However having exceptions enabled can be expensive. Delivering precise exceptions on super-scalar CPUs can be challenging and some CPUs choose to implement this by disabling FPU parallelism when floating-point exceptions are enabled. This hurts performance. The PowerPC CPU used in the Xbox 360 CPU (and presumably the one used in the PS3) slows down significantly when any floating-point exceptions are enabled. This means that when using this technique on these processors you should just enable FPU exceptions on an as-needed basis.

Sample code

The sample code below calls TryDivByZero() three times – once in the default environment, once with the three ‘bad’ floating-point exceptions enabled, and once with them suppressed again. TryDivByZero does a floating-point divide-by-zero inside a Win32 __try/__except block in order to catch exceptions, print a message, and allow the tests to continue. This type of structured exception handling block should not (repeat not) be used in production code, except possibly to record crashes and then exit. I hesitate to demonstrate this technique because I fear it will be misused. Continuing after unexpected structured exceptions is pure evil.

When generating SSE code I sometimes see STATUS_FLOAT_MULTIPLE_TRAPS instead of STATUS_FLOAT_DIVIDE_BY_ZERO. This is slightly less helpful, but the root cause should be straightforward to determine.

That said, determining the root cause can be slightly tricky. On the x87 FPU, floating-point exception reporting is delayed. Your program won’t actually crash until the next floating-point instruction after the the problematic one. In the example below the fdiv does the divide by zero, but the crash doesn’t happen until the fstp after.

Normally it is easy enough to look back one instruction to find the culprit, but sometimes the gap can be long enough to cause confusion. Luckily the CPU records the address of the actual faulting instruction and this can be retrieved from the exception record. This value is printed out when applicable in my exception handler, or you can see it in the Visual Studio registers window.

The sample code can be downloaded as a VisualC++ 2010 project (32-bit and 64-bit) from here:

Handle and continue

If you want to get really crazy/sophisticated then it is possible to catch a floating-point exception with __try/__except, handle it in some domain specific way (handling overflow by scaling down the result and recording that you did that) and then resume. This is sufficiently esoteric that I have no more to say about it – consult the documentation for _fpieee_flt if this sounds interesting.

SIMD

SSE and its SIMD instructions throw a few wrinkles into the mix. One thing to be aware of is that instructions like reciprocal estimate (rcpps) never trigger divide-by-zero exceptions – they just silently generate infinity. Therefore they are a way that infinity can be generated even when the ‘bad’ exceptions are enabled.

Additionally, many common patterns for SIMD instructions only use some components of the four-wide registers. This could be because the code is operating on a three-float vector, or it could be because the code is operating on an array of floats that is not a multiple of four long. Either way, the ‘unused’ component or components in the registers may end up triggering floating-point exceptions. These exceptions are false-positives (they don’t indicate a bug), but they must be dealt with in order to allow floating-point exceptions to be enabled. The best way to deal with this is to ensure that the unused components are filled with valid data, at least in the development builds where floating-point exceptions are enabled. Filling them with one or zero is generally good enough.

Filling the unused components with valid values may also improve performance. Some CPUs drop to microcode when they encounter some ‘special’ numbers (NaNs, infinities, and/or denormals) and using well behaved values avoids that risk.

Practical experience

On some projects I have been able to enable these three floating-point exceptions, fix all of the accidental-but-unimportant exceptions, and then find a few crucial bugs hidden in the weeds. On these projects, enabling floating-point exceptions during development was crucial. On other projects – big messy projects with a lot of history and large teams – I was unable to get the team to buy off on the concept, so it ultimately didn’t work.

Your mileage may vary, but as with asserts of any type, enabling them early, and ensuring that violations get fixed promptly, is the trick to getting value from floating-point exceptions. Adding them to a large existing codebase is trickier, but can be dealt with by only enabling them in particular parts of the code where their value exceeds their cost.

Practical experience, hot off the presses

I’ve been trying to improve the usability of debug builds on my current project and one persistent problem was a NaN that would show up in the particle system early on, triggering many different asserts. I couldn’t tell where this NaN was being generated so I knew I had to enable floating-point exceptions, using the classes described above. This project was not designed to have floating-point exceptions enabled so there were several challenges. The process was:

Enable floating-point exceptions in three key functions that called out to all of the particle system code

Disable floating-point exceptions in one child function that did floating-point overflow by design

Pad one of our arrays of particle system data with valid data to the next multiple of four so that the unused SIMD lanes wouldn’t trigger spurious exceptions

Find and fix five bugs that were causing floating-point exceptions

It worked. All of the bugs were worth fixing, and one of them was the source of the NaNs. After most of a day of investigation the crucial fix was to change one letter – from ‘e’ to ‘t’ – and this was enough to prevent us from dividing zero by zero. Now our debug builds are significantly more usable, and a genuine bug that was causing (apparently unnoticed) glitches is gone.

Homework

The summary is that while floating-point exceptions, even the ‘bad’ ones, aren’t necessarily bad, you can often find bugs in your code by treating them as errors. By using the classes shown above, with appropriate #ifdefs so that they go away in retail builds, you can enable floating-point exceptions in most parts of your code, and thereby improve reliability and avoid unexpected behavior.

But please, don’t use the __try/__except block, in debug or in retail code. It is an ugly and dangerous hack that should only be used in specialized demonstration code.