Search code examples
c++gccsigfpefenv

C++ setting floating point exception environment


I'm struggling trying to set the std::fenv in a portable way.

Based on this cppreference page, it seems that fesetexceptflag(const std::fexcept_t*,int) should help me do the trick. On the other hand, I found out that GNU offers also the feenableexcept(int) function. My understanding is that feenablexcept is GNU specific, and, although I'm highly likely to always have access to GNU stuff, I was hoping to just use std stuff, that is, stick with fesetexceptflag. I wrote a little test, and found out that the feenableexcept approach works, while the fesetexceptflag one doesn't. Here are the two examples. Swap comments in the two lines at the beginning of main to obtain version 1 (fesetexceptflag) and version 2 (feenableexcept):

#include <cfenv>
#include <csignal>
#include <cstdio>

void signal_handler (int signum) {
  printf ("signal %d caught.\n",signum);
  exit(1);
}

int main(int,char**){
  std::fexcept_t my_flag = FE_DIVBYZERO;
  fesetexceptflag(&my_flag,FE_ALL_EXCEPT); // Uncomment this for version 1
  // feenableexcept(my_flag); // Uncomment this for version 2

  int mask = fetestexcept(FE_ALL_EXCEPT)
  printf("current mask: %d\n",mask);
  printf("mask is FE_DIVBYZERO: %s\n",mask==FE_DIVBYZERO ? "yes" : "no");
  signal(SIGFPE, signal_handler);

  double one  = 1.0;
  double zero = 0.0;
  double my_inf = one/zero;
  printf("All done!\n");
  return 0;
}

Version 1 output:

current mask: 4
mask is FE_DIVBYZERO: yes
All done!

Version 2 output:

current mask: 0
mask is FE_DIVBYZERO: no
signal 8 caught.

So it seems that version 1 sets the exception flags correctly in the fenv, but fails to raise SIGFPE, while version 2 does not set the exception flags, but does raise SIGFPE. What's happening here? Am I misinterpreting the documentation of fesetexceptflag? My understanding was that it grabs all bits in the first arg that are active in the second arg, and puts them in the fenv (which is what seems to happen). But then, it seems ineffective. On the other hand, version 2 has a 0 mask fenv, and yet succeeds in raising SIGFPE. I'm very confused.

I'm using gcc 8.2.0 on a linux machine (Red Hat), if that can help.


Solution

  • Am I misinterpreting the documentation of fesetexceptflag?

    Yes. fesetexceptflag means: Set this exception flag to signify that an exception has been reported.

    A correct usage of fetestexcept is:

    feclearexcept(FE_ALL_EXCEPT);
    int mask = fetestexcept(FE_ALL_EXCEPT);
    printf("current mask: %d\n",mask);
    printf("FE_DIVBYZERO before: %s\n",std::fetestexcept(FE_DIVBYZERO) ? "yes" : "no"); // no
    double my_inf = one/zero;
    int mask = fetestexcept(FE_ALL_EXCEPT);
    printf("current mask: %d\n",mask);
    printf("FE_DIVBYZERO after: %s\n",std::fetestexcept(FE_DIVBYZERO) ? "yes" : "no"); // yes
    

    There is no standard way to make floating point exceptions raise a signal. That is the feature that glibc provides you. You can raise the signal yourself though:

    if (fetestexcept(FE_ALL_EXCEPT))
        raise(SIGFPE);