Someone sent me a question yesterday that boiled down to the difference between kinds of numeric exceptions. I’ll give my response below, but first a little background.
Numeric exceptions occur when a computer does some operation on a number that produces an error. The abstraction that lets us think of computer numbers as mathematical numbers has sprung a leak. On Windows you may see output that looks like -1.#IND
or 1.#INF
. On Linux you may see nan
or inf
. These values do not correspond to mathematical real numbers but instead are codes saying what went wrong.
If you’ve heard of NaNs (NaN stands for “not a number”) you might call every numerical exception a NaN. That’s reasonable since indeed an exception is “not a number”, or at least not an ordinary number. The problem is that NaN has a more restricted technical meaning that excludes some kinds of exceptions.
An infinite values is an exception but not a NaN. The difference is important. Some mathematical operations still make sense on an infinite value, but no operations make sense on a NaN. For example, if two floating point values are infinite and have the same sign, they are equal. But a NaN cannot equal anything, not even itself. So in C, if x
is a double
then the test x == x
will return true if x
is infinite but not if x
is a NaN.
The question that motivated this post had assumed that an infinite value was a NaN.
No, and infinity is not a NaN.
It all makes sense when you think about it. A NaN is a computer’s way of saying “I don’t know what else to do.” An infinity is the computer saying “It’s bigger than I can handle, but I’ll preserve the sign.”
For example, let
x
be the largest finite number a computer can represent. What is2*x
? Too big to represent, but it’s positive, so it’s +infinity. What’s-2*x
? It’s -infinity.But what is
sqrt(-1)
? It’s not big, so it’s not infinity. It’s just complex. Nothing else makes sense, so the computer returns NaN.
Windows displays infinite results as 1.#INF
or -1.#INF
depending on the sign. Linux displays inf
or -inf
. Windows displays NaNs as -1.#IND
(“ind” for “indeterminate”) and Linux displays nan
.
For more details see these notes: IEEE floating point exceptions in C++
“When a processor”.
When a processor tries to compute sqrt(-1) it returns NaN or an exception or both.
Because my computer processes sqrt(-1) just fine.
Hi, John. With respect, your suggestions on that page are not very “C++-ish”.
There is a standard way to deal with some of this in C++, ever since the standard was ratified in 1998. That means it works on Linux, Windows, Mac, and any compiler now and forever that implements the standard. It has other advantages too…
The only missing piece is “fpclassify”, which is not available prior to C++0X.
The standard approach is numeric_limits<>, which works like this:
#include <limits>
typedef double real_t;
real_t min_denorm = std::numeric_limits<real_t>::denorm_min();
// static functions also exist for infinity, quiet NaN, signalling NaN, etc.
Not only is this standard, but you could also “typdef” real_t to “long double” instead and all of your other code will still work. You could also define your own numeric-ish class:
class MyAwesomeFloat {
// 365 bits of precision (366 during leap years)!! Awesome!
…
};
…and then you specialize the std::numeric_limits<> template to provide implementations for infinity, NaN, etc. Then you can typedef “real_t” to “MyAwesomeFloat” and again no uses would have to re-write their code.
So this is not just the standard way; the general approach (using a “traits” class for compile-time polymorphism) is a very typical and useful C++ pattern.
fpclassify is unfortunately missing from the C++98 standard :-(. It is, however, provided by both Boost and by the soon-to-be-ratified C++0X standard. Even if you roll your own implementation, making it a template class would make it more flexible and general than a simple C-style function. (And unlike run-time polymorphism provided by virtual functions, this sort of compile-time polymorphism carries zero run-time performance cost.)
This discussion reminds me of one I had a long time ago when I asked about the difference between “Does not exist” and “undefined” in math class. The explanation I got was that “Does not exist” means that the value in question could possible exist, but does not due to inconsistency (eg lim x -> 0 ( 1/x ). In this case, taking limits from the left or right produces different answers, so the limit doesn’t exist, even though it could, in theory). “Undefined” is just something that doesn’t make sense (eg limit x -> -1 ( sqrt(x) ). Since -1 isn’t in the domain of sqrt(x), the limit makes no semantic sense). Maybe it’s more a philosophical observation, but the ideas seem to make sense applied here to NaNs and inf values.
I don’t recall the details but some of my coworkers and I were just laughing a couple weeks ago about some software returning -NaN.
Technically the strings
1.#INF
ornan
don’t come from the OS but from the C standard library, I guess. So if you use gcc on Windows you’d get the Linux ones as well.The “nan” and “inf” output (along with a set of possible variants) is required by C99. I understand you as meaning MSVC when you say “Windows”, and MSVC doesn’t try to comply to C99. Gcc (not “Linux”; gcc runs on Windows too) uses these strings to be standard-compliant, rather than introducing a platform difference.