Search code examples
c++nanfloating-point-exceptionsvariable-initializationsignaling

How do I get C++ signaling_nan() to throw an exception, without needing if tests?


I am trying to use signaling_nan() to automatically throw an exception if a variable whose value will be set post-initialization, is used before being set.

I have tried using the floating point exception functionalities via fenv.h, however that solution needs an if test for FE_INVALID, which I would like to avoid.

I am including a reduced example code below. In the code I have a class A with a member double x, which I initialize to a signaling_nan() in the constructor. The class also includes a function divide_by_x, which divides the argument passed in by the class member x. I create an object A a1, and call a1.divide_by_x(10.0). I expected the program to thrown an exception, however it proceeds and returns nan as a value for the divide_by_x function call.

In the example code I have included an if test for the FE_INVALID floating point exception at the end of the program, and its output changes along expected lines if x is initialized with a quiet_nan() [Not invalid] vs signaling_nan() [FE_INVALID]. This indicates that the signaling_nan() is indeed generating a floating point exception, but the program continues executing.

#include<iostream>
#include <utility>
#include <limits>
#include <float.h>
#include <signal.h>
#include <fenv.h>

using namespace std;

class A
{
public:

  // Constructor
  A():x(std::numeric_limits<double>::signaling_NaN()){}
  //A():x(std::numeric_limits<double>::quiet_NaN()){}

  // Set x
  void set_x(double x_value) { x = x_value; }

  // Divide number by x
  double divide_by_x(double other_number)
  {
    double result = other_number/x;

    return result;
  }
  
  // Member
  double x;
};
  
int main(void)
{
  feclearexcept(FE_ALL_EXCEPT);
  
  A a1;

  double before_setting_x;

  before_setting_x = a1.divide_by_x(10.0);

  if (fetestexcept(FE_INVALID))
        printf("FE_INVALID\n");
    else
        printf("Not invalid\n");
  
 return 0;
}

Output with x initialized with signaling_nan():

FE_INVALID

Output with x initialized with quiet_nan():

Not invalid


Solution

  • So, there's a lot of ways to go about this problem:

    Custom Classes, Which Check for FPE in Operations

    The simplest way is just to write a wrapper class, that checks for floating-point exceptions, and then never have to deal with it later, with all the numeric operations built-in.

    #include <exception>
    #include <fenv.h>
    
    class FloatingPointException: public std::exception {
        const char* what() const throw() {
            return "Exception raised in floating-point operation.";
        }
    };
    
    class F64 {
    public:
        F64(double f): m_f(f) {}
        F64(const F64 &other): m_f(other.m_f) {} 
        F64(F64 &&other): m_f(other.m_f) {}
        F64& operator=(const F64& other) { m_f = other.m_f; return *this; }
        F64& operator=(F64&& other) { m_f = other.m_f; return *this; }
    
        operator double() const { return m_f; }
    
        // This is where we use fenv.
        F64 operator*(F64 other) const {
            feclearexcept(FE_ALL_EXCEPT);
            auto result = m_f * other.m_f;
            if (fetestexcept(FE_INVALID)) {
                throw FloatingPointException();
            }
            return F64(result);
        }
    
        F64& operator*=(F64 other) {
            operator=(operator*(other));
            return *this;
        }
    
        // This is where we use fenv.
        F64 operator/(F64 other) const {
            feclearexcept(FE_ALL_EXCEPT);
            auto result = m_f / other.m_f;
            if (fetestexcept(FE_INVALID)) {
                throw FloatingPointException();
            }
            return F64(result);
        }
    
        F64& operator/=(F64 other) {
            operator=(operator/(other));
            return *this;
        }
    
        // Implement all other operations.
    private:
        double m_f;
    };
    

    feenableexcept

    Another approach is to use feenableexcept at the start of the program:

    #include <fenv.h>
    int main() {
        feenableexcept(FE_INVALID);
        ...
        return 0;
    }
    

    This will produce a SIGFPE if an invalid floating-point exception occurs. You can also use a GCC extension so this automatically occurs before main.

    #define _GNU_SOURCE 1
    #include <fenv.h>
    static void __attribute__ ((constructor)) trapfpe {
        feenableexcept(FE_INVALID);
    }
    

    You can also use this with signal handlers.

    Bespoke Assembly

    I highly recommend not doing this, since it won't be portable (even on x86, since it would differ for x87 FPUs vs. newer x86 CPUs with SSE2. However, you could try to use inline assembly to check for floating-point exceptions, even though this is likely a very bad idea. For example, dealing with the x87 FPU status register is detailed here.