Alex on software development and music


Understanding NaN Numbers in C++ and their properties

NaN, or “Not a Number,” represents values that cannot be expressed within the realm of real numbers. Its peculiar nature makes handling NaN values in programming, particularly in C++, a topic of interest for many developers. This article delves into what NaN numbers are, how they can arise, their properties, and how to deal with them effectively in C++.

How NaN Numbers Are Generated

NaN values can result from operations that do not yield a definitive or real number result. Common examples include:

  • Division of zero by zero;
  • Division of infinity by infinity;
  • Multiplication of zero by infinity;
  • Adding oppositely signed infinities;
  • Calculating the square root of a negative number;
  • Taking the logarithm of a negative number;
  • Engaging in non-trivial mathematical operations with a non-number operand.

Furthermore, NaN values can be explicitly created in C++ using std::nan(const char*) or std::numeric_limits<double>::quiet_NaN().

Properties of NaN

One of the defining characteristics of NaN is its behavior during comparisons. Any comparison of a NaN value, even with itself, using the equality operator ==, returns false. This is illustrated by:

double nanValue = 0.0 / 0.0;
bool alwaysFalse = nanValue == nanValue; // false
bool alwaysTrue = nanValue != nanValue; // true


To determine if a value is NaN, a comparison of the value to itself is surprisingly effective:

bool isNan = nanValue != nanValue;

Also, in C++, there is a built-in function std::is_nan (starting from C++11), which allows you to check if a number is NaN.

The Impact of NaN on Data Structures

Therefore, if you received NaN as a result of a calculation and this value is then used, for example, in an associative container:

std::set<double> test;

test.insert(NaNvalue);

test.insert(1.0); // will not be inserted test.insert(2.0); // will not be inserted

After this, no inserts into the set are possible, as NaN does not satisfy the strict weak ordering for keys of associative containers.

Standards and Types of NaN

NaN numbers are standardized under IEEE 754, which categorizes them into “quiet NaNs” (qNaNs) and “signaling NaNs” (sNaNs). Quiet NaNs allow computations to proceed without interruption, silently propagating through arithmetic operations. Signaling NaNs, however, trigger exceptions for immediate handling of invalid operations. The distinction between these NaN types, while significant, is not explicitly handled by C++ or the IEEE 754 standard’s library interface, depending more on the underlying hardware and compiler behaviors.

Checking for NaN

Various mathematical libraries have a NaN check function, for example, glm has a function that checks the components of a vector for NaN and returns a vector of bools:

glm::dvec3 NaNvector = someFunction();

bool isNaN = glm::all(glm::isnan(NaNvector));

A good practice is to check a number not only for NaN but also using a more thorough check – whether the number is finite. For this, std has the function std::isfinite (starting from C++11). A similar function for vectors is also available in glm.

std::println("{}", std::isfinite(std::numeric_limits<double>::quiet_NaN()));

std::println("{}", std::isfinite(std::numeric_limits<double>::infinity()));


std::println("{}", std::isfinite(-std::numeric_limits<double>::infinity()));

std::println("{}", std::isfinite(0.0));

std::println("{}", std::isfinite(std::exp(1000)));

std::println("{}", std::isfinite(std::numeric_limits<double>::min()));

Conclusion

Handling NaN values in C++ requires an understanding of their generation, properties, and impact on data structures. By utilizing built-in functions and adhering to best practices, developers can manage NaN values effectively, ensuring that their software can handle edge cases gracefully and maintain numerical integrity throughout computations.





Leave a comment