/* An IEEE 754 "exception" is not a crash. By default each one sets a sticky
   status flag and the operation returns a value -- NaN, an infinity, a rounded
   result -- and the program carries on. <fenv.h> reads the flags.

   Build: cc -std=c17 -Wall -Wextra -pedantic float_flags_c.c -lm -o float_flags_c

   The operands are volatile so every operation happens at run time. With
   constant operands a compiler may work the answer out while compiling, and a
   flag raised inside the compiler is never raised in the program. */
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#pragma STDC FENV_ACCESS ON
#include <fenv.h>
#include <float.h>
#include <math.h>
#include <stdio.h>

static void show(const char *what, double result)
{
    /* Read the flags before printf gets a chance to do any arithmetic. */
    int raised = fetestexcept(FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW
                              | FE_UNDERFLOW | FE_INEXACT);
    if (isnan(result))
        printf("%-18s = %-24s raised:", what, "NaN");
    else
        printf("%-18s = %-24.17g raised:", what, result);
    if (raised & FE_INVALID)   printf(" FE_INVALID");
    if (raised & FE_DIVBYZERO) printf(" FE_DIVBYZERO");
    if (raised & FE_OVERFLOW)  printf(" FE_OVERFLOW");
    if (raised & FE_UNDERFLOW) printf(" FE_UNDERFLOW");
    if (raised & FE_INEXACT)   printf(" FE_INEXACT");
    if (!raised)               printf(" (none)");
    printf("\n");
}

int main(void)
{
    volatile double minus_42 = -42.0, one = 1.0, zero = 0.0;
    volatile double tenth = 0.1, fifth = 0.2, biggest = DBL_MAX, smallest = DBL_MIN;
    double r;

    /* One operation at a time, clearing the flags before each. */
    feclearexcept(FE_ALL_EXCEPT);
    r = one + one;           show("1.0 + 1.0", r);
    feclearexcept(FE_ALL_EXCEPT);
    r = sqrt(minus_42);      show("sqrt(-42.0)", r);
    feclearexcept(FE_ALL_EXCEPT);
    r = one / zero;          show("1.0 / 0.0", r);
    feclearexcept(FE_ALL_EXCEPT);
    r = zero / zero;         show("0.0 / 0.0", r);
    feclearexcept(FE_ALL_EXCEPT);
    r = biggest * 2.0;       show("DBL_MAX * 2.0", r);
    feclearexcept(FE_ALL_EXCEPT);
    r = smallest / 3.0;      show("DBL_MIN / 3.0", r);
    feclearexcept(FE_ALL_EXCEPT);
    r = tenth + fifth;       show("0.1 + 0.2", r);

    /* Sticky: a flag stays raised until something clears it. An exact
       operation afterwards does not lower it. */
    printf("\nwithout clearing in between:\n");
    feclearexcept(FE_ALL_EXCEPT);
    r = sqrt(minus_42);
    r = one / zero;
    r = one + one;           show("... then 1.0 + 1.0", r);

    printf("\nno signal, no stop: the program is still running\n");
    return 0;
}
