/*- * Copyright (c) 2004 David Schultz * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Test the correctness and C99-compliance of various fenv.h features. */ #include __FBSDID("$FreeBSD$"); #include #include #include "fenv.h" #include #include #include #include #include #include /* * Implementations are permitted to define additional exception flags * not specified in the standard, so it is not necessarily true that * FE_ALL_EXCEPT == ALL_STD_EXCEPT. */ #define ALL_STD_EXCEPT (FE_DIVBYZERO | FE_INEXACT | FE_INVALID | \ FE_OVERFLOW | FE_UNDERFLOW) #pragma STDC FENV_ACCESS ON static void raiseexcept(int excepts); static void trap_handler(int sig); static int getround(void); static int expecting_trap; #ifdef __alpha__ inline double copysign(double x, double y) { __asm ("cpys %1, %2, %0" : "=f" (x) : "f" (y), "f" (x)); return (x); } #elif defined(__ia64__) inline double copysign(double x, double y) { __asm ("fmerge.s %0 = %1,%2" : "=f" (x) : "f" (y), "f" (x)); return (x); } #endif int main(int argc, char *argv[]) { fenv_t env; fexcept_t except; struct sigaction act; sigemptyset(&act.sa_mask); act.sa_flags = 0; act.sa_handler = trap_handler; if (sigaction(SIGFPE, &act, NULL)) err(1, "sigaction()"); /* * These tests check the default FP environment, so they must * be first. Although POSIX does not require the initial * values of the exception flags to have any particular value, * it seems reasonable to assume that they are all clear. * However, the memcmp() test below may be too much to ask for. */ #if 0 fegetenv(&env); assert(memcmp(&env, FE_DFL_ENV, sizeof(env)) == 0); assert(fetestexcept(FE_ALL_EXCEPT) == 0); #endif /* All exception flags are clear at this point. */ fegetexceptflag(&except, FE_ALL_EXCEPT); raiseexcept(FE_INVALID); assert(fetestexcept(ALL_STD_EXCEPT) == FE_INVALID); assert(fetestexcept(FE_INVALID) == FE_INVALID); assert(fetestexcept(ALL_STD_EXCEPT & ~FE_INVALID) == 0); raiseexcept(FE_DIVBYZERO); assert(fetestexcept(FE_DIVBYZERO) == FE_DIVBYZERO); raiseexcept(FE_INEXACT); assert(fetestexcept(ALL_STD_EXCEPT) == (FE_INVALID | FE_DIVBYZERO | FE_INEXACT)); raiseexcept(FE_OVERFLOW); assert(fetestexcept(ALL_STD_EXCEPT) == (FE_INVALID | FE_DIVBYZERO | FE_INEXACT | FE_OVERFLOW)); raiseexcept(FE_UNDERFLOW); assert(fetestexcept(ALL_STD_EXCEPT) == (FE_INVALID | FE_DIVBYZERO | FE_INEXACT | FE_OVERFLOW | FE_UNDERFLOW)); assert(fetestexcept(ALL_STD_EXCEPT) == ALL_STD_EXCEPT); raiseexcept(FE_ALL_EXCEPT); fesetexceptflag(&except, FE_ALL_EXCEPT); assert(fetestexcept(FE_ALL_EXCEPT) == 0); /* All exception flags are clear at this point. */ raiseexcept(FE_ALL_EXCEPT); assert(fetestexcept(ALL_STD_EXCEPT) == ALL_STD_EXCEPT); feclearexcept(FE_ALL_EXCEPT); assert(fetestexcept(FE_ALL_EXCEPT) == 0); raiseexcept(ALL_STD_EXCEPT); feclearexcept(FE_DIVBYZERO); assert(fetestexcept(ALL_STD_EXCEPT) == (ALL_STD_EXCEPT & ~FE_DIVBYZERO)); fegetexceptflag(&except, FE_INEXACT | FE_OVERFLOW); feclearexcept(FE_ALL_EXCEPT); fesetexceptflag(&except, FE_INEXACT); assert(fetestexcept(FE_ALL_EXCEPT) == FE_INEXACT); feclearexcept(FE_ALL_EXCEPT); assert(fetestexcept(FE_ALL_EXCEPT) == 0); /* All exception flags are clear at this point. */ #if 0 feraiseexcept(FE_INEXACT); assert(fetestexcept(FE_ALL_EXCEPT) == FE_INEXACT); feraiseexcept(FE_DIVBYZERO | FE_UNDERFLOW); assert(fetestexcept(FE_ALL_EXCEPT) == (FE_INEXACT | FE_DIVBYZERO | FE_UNDERFLOW)); feclearexcept(FE_ALL_EXCEPT); assert(fetestexcept(FE_ALL_EXCEPT) == 0); #endif /* All exception flags are clear at this point. */ raiseexcept(FE_DIVBYZERO); fesetmask(FE_ALL_EXCEPT & ~ALL_STD_EXCEPT); feholdexcept(&env); assert(fetestexcept(ALL_STD_EXCEPT) == 0); expecting_trap = 1; feupdateenv(&env); assert(expecting_trap == 0); fegetenv(&env); assert(fetestexcept(ALL_STD_EXCEPT) == 0); expecting_trap = 1; raiseexcept(FE_INVALID); assert(expecting_trap == 0); fesetenv(&env); assert(fetestexcept(ALL_STD_EXCEPT) == 0); fesetmask(FE_ALL_EXCEPT); assert(fegetround() == FE_TONEAREST); assert(getround() == FE_TONEAREST); fesetround(FE_DOWNWARD); assert(fegetround() == FE_DOWNWARD); assert(getround() == FE_DOWNWARD); fesetround(FE_UPWARD); assert(getround() == FE_UPWARD); assert(fegetround() == FE_UPWARD); fesetround(FE_TOWARDZERO); assert(getround() == FE_TOWARDZERO); assert(fegetround() == FE_TOWARDZERO); fesetround(FE_TONEAREST); assert(getround() == FE_TONEAREST); fesetround(FE_UPWARD); fegetenv(&env); fesetround(FE_DOWNWARD); assert(getround() == FE_DOWNWARD); fesetenv(&env); assert(getround() == FE_UPWARD); printf("PASS fenv\n"); return (0); } /* * Raise a floating-point exception without relying on the standard * library routines, which we are trying to test. * * XXX We can't raise an {over,under}flow without also raising an * inexact exception. */ static void raiseexcept(int excepts) { volatile double d; /* * With a compiler that supports the FENV_ACCESS pragma * properly, simple expressions like '0.0 / 0.0' should * be sufficient to generate traps. Unfortunately, we * need to bring a volatile variable into the equation * to prevent incorrect optimizations. */ if (excepts & FE_INVALID) { d = 0.0; d = 0.0 / d; } if (excepts & FE_DIVBYZERO) { d = 0.0; d = 1.0 / d; } if (excepts & FE_OVERFLOW) { d = DBL_MAX; d *= 2.0; } if (excepts & FE_UNDERFLOW) { d = DBL_MIN; d /= DBL_MAX; } if (excepts & FE_INEXACT) { d = DBL_MIN; d += 1.0; } } /* * Determine the current rounding mode without relying on the fenv * routines. This function may raise an inexact exception. */ static int getround(void) { volatile double d; d = 0.0; d -= 0.0; if (copysign(1.0, d) < 0.0) return (FE_DOWNWARD); d = 1.0; if (d + (DBL_EPSILON * 3.0 / 4.0) == 1.0) return (FE_TOWARDZERO); if (d + (DBL_EPSILON * 1.0 / 4.0) > 1.0) return (FE_UPWARD); return (FE_TONEAREST); } static void trap_handler(int sig) { assert(expecting_trap); expecting_trap = 0; }