Recently we had occasional, but serious problems with some software we’re making. The problems were with invalid floating point numbers or C++ NAN and IND/INF errors.
Once invalid (#IND
/ #INF
/ #NAN
) numbers have infested your simulation, it’s very difficult to get rid of it. It’s like a viral infection. The best way to avoid invalid floating point numbers are to prevent them happening in the first place.
What happened was that invalid floating point values were introduced while calculating the angle between two vectors. The acos method calculate the angle, and it’s domain is [-1, 1]
. Due to rounding errors, the actual value passed to acos
was slightly less or slightly above the domain, which resulted in an invalid number.
The way we caught the error was to modify the vector3d-class and insert breakpoints when the expression value != value
is true. Only NAN
and IND
values behave like that.
After the breakpoints were set, the call stack gave it all away.
Here is a sample program for detecting invalid numbers.
#include <iostream> // cout #include <math.h> // acos #include <float.h> // DBL_MAX #include <limits> // numeric_limits template<typename T> bool is_infinite( const T &value ) { // Since we're a template, it's wise to use std::numeric_limits<T> // // Note: std::numeric_limits<T>::min() behaves like DBL_MIN, and is the smallest absolute value possible. // T max_value = std::numeric_limits<T>::max(); T min_value = - max_value; return ! ( min_value <= value && value <= max_value ); } template<typename T> bool is_nan( const T &value ) { // True if NAN return value != value; } template<typename T> bool is_valid( const T &value ) { return ! is_infinite(value) && ! is_nan(value); } int main() { using std::cout; double a, b, c, d, e; a = 1.0; b = 0.0; c = a / c; // divide by zero d = acos(-1.001); // domain for acos is [-1, 1], anything else is #IND or inf e = b / b; // zero / zero cout << "Value of a: " << a << " " << is_valid(a) << " " << (is_nan(a) ? " nan " : "") << (is_infinite(a) ? " infinite " : "") << "n"; cout << "Value of b: " << b << " " << is_valid(b) << " " << (is_nan(b) ? " nan " : "") << (is_infinite(b) ? " infinite " : "") << "n"; cout << "Value of c: " << c << " " << is_valid(c) << " " << (is_nan(c) ? " nan " : "") << (is_infinite(c) ? " infinite " : "") << "n"; cout << "Value of d: " << d << " " << is_valid(d) << " " << (is_nan(d) ? " nan " : "") << (is_infinite(d) ? " infinite " : "") << "n"; cout << "Value of e: " << e << " " << is_valid(e) << " " << (is_nan(e) ? " nan " : "") << (is_infinite(e) ? " infinite " : "") << "n"; return 0; }
Output is:
Value of a: 1 1 Value of b: 0 1 Value of c: inf 0 infinite Value of d: nan 0 nan infinite Value of e: -nan 0 nan infinite
Professional Software Developer, doing mostly C++. Connect with Kent on Twitter.