/***********************************************************************
C program to test the setting of floating-point exception flags on Sun
Solaris.  Although all IEEE 754 implementations have these flags, the
interface to them is unique to each vendor.  Eventually, a common
interface needs to be written to allow exception-flag access
everywhere.
[26-Jul-2001]
***********************************************************************/

#if defined(HAVE_LONG_DOUBLE)
typedef long double LONG_DOUBLE;
#else
typedef double LONG_DOUBLE;
#endif

#include <stdio.h>
#include <stdlib.h>

#include "args.h"

#if defined(__sun) || defined(sun)
#include <ieeefp.h>
#else
/* This program is not expected to work correctly anywhere except on Sun
   Solaris, but provide sufficient definitions to at least allow it to
   compile, link, and run without error anywhere else. */
#define	FP_X_INV	0x10	/* invalid operation exception */
#define	FP_X_OFL	0x08	/* overflow exception */
#define	FP_X_UFL	0x04	/* underflow exception */
#define	FP_X_DZ		0x02	/* divide-by-zero exception */
#define	FP_X_IMP	0x01	/* imprecise (loss of precision) */
typedef int fp_except;
extern fp_except fpgetmask ARGS((void));		/* current exception mask */
extern fp_except fpsetmask ARGS((fp_except));		/* set mask, return previous */
extern fp_except fpgetsticky ARGS((void));		/* return logged exceptions */
extern fp_except fpsetsticky ARGS((fp_except));		/* change logged exceptions */

fp_except
fpgetmask(VOID_ARG)
{
    return ((fp_except)0);
}

#if STDC
fp_except
fpsetmask(fp_except e)
#else
fp_except
fpsetmask(e)
fp_except e;
#endif
{
    return ((fp_except)0);
}

fp_except
fpgetsticky (VOID_ARG)
{
    return ((fp_except)0);
}

#if STDC
fp_except
fpsetsticky(fp_except e)
#else
fp_except
fpsetsticky(e)
fp_except e;
#endif
{
    return ((fp_except)0);
}
#endif

int		main ARGS((void));
void		clear ARGS((void));
void		check ARGS((void));
double		dpow ARGS((double d, int p));
float		fpow ARGS((float f, int p));
LONG_DOUBLE	qpow ARGS((LONG_DOUBLE q, int p));
void		report ARGS((const char *s));
void		separator ARGS((void));

int
main(VOID_ARG)
{
    float f;
    double d;
    LONG_DOUBLE q;

    separator();
    report("\nTest at normal underflow limit\n");

    clear();
    report("\nfloat: 2^(-126)\n");
    f = fpow((float)2.0, -126);
    check();
    (void)fprintf(stderr, "f = %g\n", (double)f);

    clear();
    report("\ndouble: 2^(-1022)\n");
    d = dpow((double)2.0, -1022);
    check();
    (void)fprintf(stderr, "d = %g\n", d);

    clear();
    report("\nLONG_DOUBLE: 2^(-16382)\n");
    q = qpow((LONG_DOUBLE)2.0, -16382);
    check();
    (void)fprintf(stderr, "q = %Lg\n", q);

    separator();
    report("\nTest of divide-by-two at normal underflow limit\n");

    clear();
    report("\nfloat: 2^(-127)\n");
    f /= (float)2.0;
    check();
    (void)fprintf(stderr, "f = %g\n", (double)f);

    clear();
    report("\ndouble: 2^(-1023)\n");
    d /= (double)2.0;
    check();
    (void)fprintf(stderr, "d = %g\n", d);

    clear();
    report("\nLONG_DOUBLE: 2^(-16383)\n");
    q /= (LONG_DOUBLE)2.0;
    check();
    (void)fprintf(stderr, "q = %Lg\n", q);

    separator();
    report("\nTest of divide-by-two at normal underflow limit\n");

    clear();
    report("\nfloat: 2^(-128)\n");
    f /= (float)2.0;
    check();
    (void)fprintf(stderr, "f = %g\n", (double)f);

    clear();
    report("\ndouble: 2^(-1024)\n");
    d /= (double)2.0;
    check();
    (void)fprintf(stderr, "d = %g\n", d);

    clear();
    report("\nLONG_DOUBLE: 2^(-16384)\n");
    q /= (LONG_DOUBLE)2.0;
    check();
    (void)fprintf(stderr, "q = %Lg\n", q);

    separator();
    report("\nTest of subnormal underflow limit\n");

    clear();
    report("\nfloat: 2^(-149)\n");
    f = fpow((float)2.0, -149);
    check();
    (void)fprintf(stderr, "f = %g\n", (double)f);

    clear();
    report("\ndouble: 2^(-1074)\n");
    d = dpow(2.0, -1074);
    check();
    (void)fprintf(stderr, "d = %g\n", d);

    clear();
    report("\nLONG_DOUBLE: 2^(-16494)\n");
    q = qpow((LONG_DOUBLE)2.0, -16494);
    check();
    (void)fprintf(stderr, "q = %Lg\n", q);

    separator();
    report("\nTest of divide-by-two at subnormal underflow limit\n");

    clear();
    report("\nfloat: 2^(-150)\n");
    f /= (float)2.0;
    check();
    (void)fprintf(stderr, "f = %g\n", (double)f);

    clear();
    report("\ndouble: 2^(-1075)\n");
    d /= 2.0;
    check();
    (void)fprintf(stderr, "d = %g\n", d);

    clear();
    report("\nLONG_DOUBLE: 2^(-16495)\n");
    q /= (LONG_DOUBLE)2.0;
    check();
    (void)fprintf(stderr, "q = %Lg\n", q);

    separator();
    clear();

    return (EXIT_SUCCESS);
}

void
check(VOID_ARG)
{
    fp_except flags;
    flags = fpgetsticky();
    if (flags)
        (void)fprintf(stderr,"flags = 0x%02x\n", (unsigned int)flags);
    if (flags & FP_X_INV)
	report("flag: invalid operation\n");
    if (flags & FP_X_OFL)
	report("flag: overflow\n");
    if (flags & FP_X_UFL)
	report("flag: underflow\n");
    if (flags & FP_X_DZ)
	report("flag: divide-by-zero\n");
    if (flags & FP_X_IMP)
	report("flag: precision loss\n");
}

void
clear(VOID_ARG)
{
    (void)fpsetsticky(0);
}


#if STDC
double
dpow(double d, int p)
#else
double
dpow(d,p)
double d;
int p;
#endif
{
    double result;
    result = 1.0;
    if (p >= 0)
    {
	while (p-- > 0)
	    result *= d;
    }
    else
    {
	while (p++ < 0)
	    result /= d;
    }
    return (result);
}


#if STDC
float
fpow(float f, int p)
#else
float
fpow(f,p)
float f;
int p;
#endif
{
    float result;
    result = (float)1.0;
    if (p >= 0)
    {
	while (p-- > 0)
	    result *= f;
    }
    else
    {
	while (p++ < 0)
	    result /= f;
    }
    return (result);
}


#if STDC
LONG_DOUBLE
qpow(LONG_DOUBLE q, int p)
#else
LONG_DOUBLE
qpow(q,p)
LONG_DOUBLE q;
int p;
#endif
{
    LONG_DOUBLE result;
    result = (LONG_DOUBLE)1.0;
    if (p >= 0)
    {
	while (p-- > 0)
	    result *= q;
    }
    else
    {
	while (p++ < 0)
	    result /= q;
    }
    return (result);
}


#if STDC
void
report(const char *s)
#else
void
report(s)
const char *s;
#endif
{
    (void)fprintf(stderr,"%s",s);
}

void
separator(VOID_ARG)
{
    report("------------------------------------------------------------------------\n");
}
