简体   繁体   中英

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.

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);

Am I misinterpreting the documentation of fesetexceptflag?

Yes. When you do for example 1.0/0 then the FE_DIVBYZERO flag gets raised in the current environment. fesetexceptflag doesn't let you decide what will happen when you divide by 0. fesetexceptflag let's you check if the operation that you already did resulted in exception.

You can only see what will happen on floating-point exceptions withmath_errhandling macro. Which is a macro that only tells that either errno or floating point exceptions are used.

This little example could shed some light:

#include <cfenv>
#include <cstdio>
#pragma STDC FENV_ACCESS ON
#if math_errhandling != MATH_ERREXCEPT
   #error This code needs to use floating point exceptions
#endif

int main() {
    std::feclearexcept(FE_ALL_EXCEPT);
    printf("%s\n", std::fetestexcept(FE_DIVBYZERO) ? "FE_DIVBYZERO" : "no FE_DIVBYZERO");
    double a = 1.0/0;
    printf("%s\n", std::fetestexcept(FE_DIVBYZERO) ? "FE_DIVBYZERO" : "no FE_DIVBYZERO");
}

will output:

no FE_DIVBYZERO
FE_DIVBYZERO

With fexcept_t you can restore implementation defined representation of currently set flags. You can't do std::fexcept_t my_flag = FE_DIVBYZERO; - well, you can, but the content of fexcept_t is implementation defined, so the result will be implementation-defined. You can't modify fexcept_t manually. You can only save currently raised floating point exceptions with fegetexceptflag , do some calculations that can throw that you want to inspect, and then restore the floating point exceptions with fesetexceptflag with the same flag. You modify fexcept_t by sesetexceptflag then with feclearexcept and feraiseexcept and then saving it with fegetexceptflag .

Throwing a SIGFPE signal is an extension C99 J.5.17p1 . It could happen in addition or instead of setting errno or floating point flags. The extension is activated with the gnu function feenableexcept .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM