Search code examples
cfloating-pointfortranscientific-computing

Catch Floating Point Exceptions using a compiler option with C


Gfortran has the handy -ffpe-trap compiler option, but no similar option is available for gcc. I am vaguely aware that they handle exceptions differently, but not enough to know why one can die from FPEs just by enabling a compiler flag, but the other requires including extra code to turn exceptions on.


Solution

  • Sorry for the wall of text; the real answer is at the bottom.

    Floating point exceptions are controlled by library code in C99, not by compiler flags. Here's an example:

    #include <fenv.h>
    #include <math.h>
    #include <stdio.h>
    
    #define PRINTEXC(ex, val) printf(#ex ": %s\n", (val & ex) ? "set" : "unset");
    
    double foo(double a, double b) { return sin(a) / b; }
    
    int main()
    {
        int e;
        double x;
    
        feclearexcept(FE_ALL_EXCEPT);
    
        x = foo(1.2, 3.1);
    
        e = fetestexcept(FE_ALL_EXCEPT);
        PRINTEXC(FE_DIVBYZERO, e);
        PRINTEXC(FE_INEXACT, e);
        PRINTEXC(FE_INVALID, e);
        PRINTEXC(FE_OVERFLOW, e);
        PRINTEXC(FE_UNDERFLOW, e);
    
        putchar('\n');
    
        feclearexcept(FE_ALL_EXCEPT);
    
        x += foo(1.2, 0.0);
    
        e = fetestexcept(FE_ALL_EXCEPT);
        PRINTEXC(FE_DIVBYZERO, e);
        PRINTEXC(FE_INEXACT, e);
        PRINTEXC(FE_INVALID, e);
        PRINTEXC(FE_OVERFLOW, e);
        PRINTEXC(FE_UNDERFLOW, e);
        return lrint(x);
    }
    

    Output:

    FE_DIVBYZERO: unset
    FE_INEXACT: set
    FE_INVALID: unset
    FE_OVERFLOW: unset
    FE_UNDERFLOW: unset
    
    FE_DIVBYZERO: set
    FE_INEXACT: set
    FE_INVALID: unset
    FE_OVERFLOW: unset
    FE_UNDERFLOW: unset
    

    Update: With GNU GCC, you may be able to alternatively cause floating point exceptions to trap and send a signal:

    #pragma STDC FENV_ACCESS on
    
    #define _GNU_SOURCE
    #include <fenv.h>
    
    int main()
    {
    #ifdef FE_NOMASK_ENV
        fesetenv(FE_NOMASK_ENV);
    #endif
    
        // ...
    }
    

    However, it's not entirely clear what you should do when you receive a SIGFPE, since you can't undo the faulty instruction. (And see @EricPostpischil's comments about the pragma; thanks!)