/*
 * Copyright (c) 1994 Cygnus Support.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * and/or other materials related to such
 * distribution and use acknowledge that the software was developed
 * at Cygnus Support, Inc.  Cygnus Support, Inc. may not be used to
 * endorse or promote products derived from this software without
 * specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "test.h"
#include <errno.h>

static int
randi(void)
{
    static uint32_t next;
    next = (next * 1103515245UL) + 12345UL;
    return ((next >> 16) & 0xffff);
}

#ifndef __FLOAT_WORD_ORDER__
#define __FLOAT_WORD_ORDER__ __BYTE_ORDER__
#endif

static double
randx(void)
{
    double res;

    do {
        union {
            short  parts[4];
            double res;
        } u;

#if __FLOAT_WORD_ORDER__ == __ORDER_LITTLE_ENDIAN__
        u.parts[0] = randi();
        u.parts[1] = randi();
        u.parts[2] = randi();
        u.parts[3] = randi();
#else
        u.parts[3] = randi();
        u.parts[2] = randi();
        u.parts[1] = randi();
        u.parts[0] = randi();
#endif
        res = u.res;

    } while (!finite(res));

    return res;
}

/* Return a random double, but bias for numbers closer to 0 */
static double
randy(void)
{
    int    pow;
    double r = randx();
    r = frexp(r, &pow);
    return ldexp(r, randi() & 0x1f);
}

static void
test_frexp(void)
{
    int    i;
    double r;
    int    t;

    float  xf;
    double gives;

    int    pow;

    /* Frexp of x return a and n, where a * 2**n == x, so test this with a
       set of random numbers */
    for (t = 0; t < 2; t++) {
        for (i = 0; i < 1000; i++) {

            double x = randx();
            line(i);
            switch (t) {
            case 0:
                newfunc("frexp/ldexp");
                r = frexp(x, &pow);
                if (r > 1.0 || r < -1.0) {
                    /* Answer can never be > 1 or < 1 */
                    test_iok(0, 1);
                }

                gives = ldexp(r, pow);
                test_mok(gives, x, 62);
                break;
            case 1:
                newfunc("frexpf/ldexpf");
                if (x > (double)FLT_MIN && x < (double)FLT_MAX) {
                    /* test floats too, but they have a smaller range so make sure x
                       isn't too big. Also x can get smaller than a float can
                       represent to make sure that doesn't happen too */
                    xf = x;
                    r = (double)frexpf(xf, &pow);
                    if (r > 1.0 || r < -1.0) {
                        /* Answer can never be > 1 or < -1 */
                        test_iok(0, 1);
                    }

                    gives = (double)ldexpf(r, pow);
                    test_mok(gives, x, 32);
                }
            }
        }
    }

    /* test a few numbers manually to make sure frexp/ldexp are not
       testing as ok because both are broken */

    r = frexp(64.0, &i);

    test_mok(r, 0.5, 64);
    test_iok(i, 7);

    r = frexp(96.0, &i);

    test_mok(r, 0.75, 64);
    test_iok(i, 7);
}

/* Test mod - this is given a real hammering by the strtod type
   routines, here are some more tests.

   By definition

   modf = func(value, &iptr)

      (*iptr + modf) == value

   we test this

*/
static void
test_mod(void)
{
    int i;

    newfunc("modf");

    for (i = 0; i < 1000; i++) {
        double intpart;
        double n;
        line(i);
        n = randx();
        if (finite(n) && n != 0.0) {
            volatile double r = modf(n, &intpart);
            line(i);
            test_mok(intpart + r, n, 63);
        }
    }
    newfunc("modff");

    for (i = 0; i < 1000; i++) {
        float  intpart;
        double nd;
        line(i);
        nd = randx();
        if (fabs(nd) < (double)FLT_MAX && finitef(nd) && nd != 0.0) {
            volatile float  n = nd;
            volatile double r = (double)modff(n, &intpart);
            line(i);
            test_mok((double)intpart + r, (double)n, 32);
        }
    }
}

/*
Test pow by multiplying logs
*/
static void
test_pow(void)
{
    unsigned int i;
    newfunc("pow");

    for (i = 0; i < 1000; i++) {
        double n1;
        double n2;
        double res;
        double shouldbe;

        line(i);
        n1 = fabs(randy());
        n2 = fabs(randy() / 100.0);
        res = pow(n1, n2);
        shouldbe = exp(log(n1) * n2);
        test_mok(shouldbe, res, 55);
    }

    newfunc("powf");

    for (i = 0; i < 1000; i++) {
        float n1;
        float n2;
        float res;
        float shouldbe;

        errno = 0;

        line(i);
        n1 = fabs(randy());
        n2 = fabs(randy() / 100.0);
        res = powf(n1, n2);
        shouldbe = expf(logf(n1) * n2);
        if (!errno)
            test_mok((double)shouldbe, (double)res, 28);
    }
}

void
test_math2(void)
{
    test_mod();
    test_frexp();
    test_pow();
}
