John Vulconshinz
John Vulconshinz

Reputation: 1148

IEEE-754 Floating-point Exceptions in C

I am writing a floating-point calculator-interface, in C, that allows the mathematical functions defined in math.h to be accessed at runtime. The interface is implemented as a function that behaves like strtold(). It is based on ASCII and is supposed to be as portable as ASCII but in order for that to be true I need to handle floating-points in as portable a way as possible. I am happy with limiting support to IEEE-754 floating-points but I am not sure how to handle the exceptions defined by IEEE-754 (overflow, underflow, etc.). First of all I am pretty sure the only way to check for exceptions that will work in all rounding modes is checking the status flags themselves; in order to do that I will need fenv.h (defined in Annex F of C99) so I want to know how portable fenv.h is in practice. I also do not completely understand how fenv.h is supposed to work; it appears to me that the status flags are centralized but for whatever reason I was under the impression each floating-point had the flags built-in. Also I know C99 says functions defined in math.h may overflow and underflow but I do not understand how I am supposed to check for these exceptions. So to summarize I am looking for an example of how to use fenv.h to check for an overflow caused by multiplication and an explanation of how to properly error check the functions defined in math.h.

Upvotes: 5

Views: 826

Answers (3)

tmyklebu
tmyklebu

Reputation: 14225

In theory, the following function multiplies two numbers, returning true if an overflow occurred and false if not:

bool mul(double &a, double b) {
  feclearexcept(FE_OVERFLOW);
  a *= b;
  return fetestexcept(FE_OVERFLOW) != 0;
}

The standard states that you need to #pragma FENV_ACCESS ON in order to use this. However, I have yet to use a compiler that cares about that pragma or a compiler that knows that floating-point multiplication has a side effect reflected in the exception flags---both gcc and clang will happily "optimise away" a "dead" floating-point operation. gcc bug 34678 concerns this behaviour and I imagine there's a similar bug against clang. This caveat also applies to any use of rounding modes other than round-to-nearest-breaking-ties-to-even in a program.

Upvotes: 6

Serge Ballesta
Serge Ballesta

Reputation: 149185

If you are are building a calculator interface and want to process the floating point exceptions, you should :

  • reset flags before any floating point operation
  • test flags after the operation

You will have no help for it so you must implement if by hand. Example :

double mul(double a, double b, int *status) {
    #pragma STDC FENV_ACCESS ON
    fexcept_t flags;
    int sv_status, f_status = -1;
    double resul;

    sv_status = fegetexceptflag(&flags, FE_ALL_EXCEPT) != 0; /* save flags */
    if (sv_status == 0) {
        f_status = feclearexcept(FE_ALL_EXCEPT);   /* clear all fp exception con
ditions */
    }
    resul = a * b;
    if (f_status == 0) {
        *status = fetestexcept(FE_ALL_EXCEPT); /* note conditions */
    }
    if (sv_status == 0) {
        fesetexceptflag(&flags, FE_ALL_EXCEPT); /* restore initial flags */
    }
    return resul;

}

Demo :

int main ()
{
        double d2, d3, d4;
        int status;
        double d = 1e100;

        feraiseexcept(FE_OVERFLOW | FE_INEXACT);
        status = fetestexcept(FE_ALL_EXCEPT);
        printf("initial status : %x\n", status);
        d2 = mul(3., 4., &status);
        printf("resul 3 * 4 : %g - status %x (%x)\n", d2,
                status, fetestexcept(FE_ALL_EXCEPT));
        d2 = mul(d, d, &status);
        printf("resul d * d : %g - status %x (%x)\n", d2,
                status, fetestexcept(FE_ALL_EXCEPT));
        d2 = mul(d2, d, &status);
        printf("resul d *d *d : %g - status %x (%x)\n", d2,
                status, fetestexcept(FE_ALL_EXCEPT));
        d2 = mul(d2, d, &status);
        printf("resul d *d *d*d : %g - status %x (%x)\n", d2,
                status, fetestexcept(FE_ALL_EXCEPT));
        d2 = mul(d2, d, &status);
        printf("resul d *d *d*d*d : %g - status %x (%x)\n", d2,
                status, fetestexcept(FE_ALL_EXCEPT));
        return 0;
}

gives :

initial status : 28
resul 3 * 4 : 12 - status 0 (28)
resul d * d : 1e+200 - status 20 (28)
resul d *d *d : 1e+300 - status 20 (28)
resul d *d *d*d : inf - status 28 (28)
resul d *d *d*d*d : inf - status 0 (28)

That means that mul :

  • correctly sets the status flag
  • keeps the floating point exception flag unchanged

As this code only uses macros and functions defined in C specification it should work on any C99 compliant compiler.

Now it is up to you to actually process the flag.

References : ISO/IEC 9899:201x (ISO C11) Committee Draft

Nota : Clang (at least) issues a warning saying the it ignores pragma STDS FENV_ACCESS but it works fine

Upvotes: 5

gnasher729
gnasher729

Reputation: 52632

In reality, nobody checks for floating point exceptions. Anyone who cares (and that is a minority!) checks for the values returned: You can easily check whether a result is NaN, +/- infinity, or a denormalised number. You'd need to check by hand to detect that a zero is the result of multiplying or dividing non-zero numbers, but that is quite easy.

One reason that nobody checks for exceptions is that I would find it challenging to get it working correctly with any single compiler, and impossible to get it working with a multitude of compilers.

Upvotes: 3

Related Questions