/* Test mpn_get_d.

Copyright 2002-2004 Free Software Foundation, Inc.

This file is part of the GNU MP Library test suite.

The GNU MP Library test suite is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 3 of the License,
or (at your option) any later version.

The GNU MP Library test suite is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
Public License for more details.

You should have received a copy of the GNU General Public License along with
the GNU MP Library test suite.  If not, see https://www.gnu.org/licenses/.  */

#include "config.h"

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

#include "gmp-impl.h"
#include "tests.h"


#ifndef _GMP_IEEE_FLOATS
#define _GMP_IEEE_FLOATS 0
#endif


/* Exercise various 2^n values, with various exponents and positive and
   negative.  */
void
check_onebit (void)
{
  static const int bit_table[] = {
    0, 1, 2, 3,
    GMP_NUMB_BITS - 2, GMP_NUMB_BITS - 1,
    GMP_NUMB_BITS,
    GMP_NUMB_BITS + 1, GMP_NUMB_BITS + 2,
    2 * GMP_NUMB_BITS - 2, 2 * GMP_NUMB_BITS - 1,
    2 * GMP_NUMB_BITS,
    2 * GMP_NUMB_BITS + 1, 2 * GMP_NUMB_BITS + 2,
    3 * GMP_NUMB_BITS - 2, 3 * GMP_NUMB_BITS - 1,
    3 * GMP_NUMB_BITS,
    3 * GMP_NUMB_BITS + 1, 3 * GMP_NUMB_BITS + 2,
    4 * GMP_NUMB_BITS - 2, 4 * GMP_NUMB_BITS - 1,
    4 * GMP_NUMB_BITS,
    4 * GMP_NUMB_BITS + 1, 4 * GMP_NUMB_BITS + 2,
    5 * GMP_NUMB_BITS - 2, 5 * GMP_NUMB_BITS - 1,
    5 * GMP_NUMB_BITS,
    5 * GMP_NUMB_BITS + 1, 5 * GMP_NUMB_BITS + 2,
    6 * GMP_NUMB_BITS - 2, 6 * GMP_NUMB_BITS - 1,
    6 * GMP_NUMB_BITS,
    6 * GMP_NUMB_BITS + 1, 6 * GMP_NUMB_BITS + 2,
  };
  static const int exp_table[] = {
    0, -100, -10, -1, 1, 10, 100,
  };

  /* FIXME: It'd be better to base this on the float format. */
#if defined (__vax) || defined (__vax__)
  int     limit = 127;  /* vax fp numbers have limited range */
#else
  int     limit = 511;
#endif

  int        bit_i, exp_i, i;
  double     got, want;
  mp_size_t  nsize, sign;
  long       bit, exp, want_bit;
  mp_limb_t  np[20];

  for (bit_i = 0; bit_i < numberof (bit_table); bit_i++)
    {
      bit = bit_table[bit_i];

      nsize = BITS_TO_LIMBS (bit+1);
      refmpn_zero (np, nsize);
      np[bit/GMP_NUMB_BITS] = CNST_LIMB(1) << (bit % GMP_NUMB_BITS);

      for (exp_i = 0; exp_i < numberof (exp_table); exp_i++)
        {
          exp = exp_table[exp_i];

          want_bit = bit + exp;
          if (want_bit >= limit || want_bit <= -limit)
            continue;

          want = 1.0;
          for (i = 0; i < want_bit; i++)
            want *= 2.0;
          for (i = 0; i > want_bit; i--)
            want *= 0.5;

          for (sign = 0; sign >= -1; sign--, want = -want)
            {
              got = mpn_get_d (np, nsize, sign, exp);
              if (got != want)
                {
                  printf    ("mpn_get_d wrong on 2^n\n");
                  printf    ("   bit      %ld\n", bit);
                  printf    ("   exp      %ld\n", exp);
                  printf    ("   want_bit %ld\n", want_bit);
                  printf    ("   sign     %ld\n", (long) sign);
                  mpn_trace ("   n        ", np, nsize);
                  printf    ("   nsize    %ld\n", (long) nsize);
                  d_trace   ("   want     ", want);
                  d_trace   ("   got      ", got);
                  abort();
                }
            }
        }
    }
}


/* Exercise values 2^n+1, while such a value fits the mantissa of a double. */
void
check_twobit (void)
{
  int        i, mant_bits;
  double     got, want;
  mp_size_t  nsize, sign;
  mp_ptr     np;

  mant_bits = tests_dbl_mant_bits ();
  if (mant_bits == 0)
    return;

  np = refmpn_malloc_limbs (BITS_TO_LIMBS (mant_bits));
  want = 3.0;
  for (i = 1; i < mant_bits; i++)
    {
      nsize = BITS_TO_LIMBS (i+1);
      refmpn_zero (np, nsize);
      np[i/GMP_NUMB_BITS] = CNST_LIMB(1) << (i % GMP_NUMB_BITS);
      np[0] |= 1;

      for (sign = 0; sign >= -1; sign--)
        {
          got = mpn_get_d (np, nsize, sign, 0);
          if (got != want)
            {
              printf    ("mpn_get_d wrong on 2^%d + 1\n", i);
              printf    ("   sign     %ld\n", (long) sign);
              mpn_trace ("   n        ", np, nsize);
              printf    ("   nsize    %ld\n", (long) nsize);
              d_trace   ("   want     ", want);
              d_trace   ("   got      ", got);
              abort();
            }
          want = -want;
        }

      want = 2.0 * want - 1.0;
    }

  free (np);
}


/* Expect large negative exponents to underflow to 0.0.
   Some systems might have hardware traps for such an underflow (though
   usually it's not the default), so watch out for SIGFPE. */
void
check_underflow (void)
{
  static const long exp_table[] = {
    -999999L, LONG_MIN,
  };
  static const mp_limb_t  np[1] = { 1 };

  static long exp;
  mp_size_t  nsize, sign;
  double     got;
  int        exp_i;

  nsize = numberof (np);

  if (tests_setjmp_sigfpe() == 0)
    {
      for (exp_i = 0; exp_i < numberof (exp_table); exp_i++)
        {
          exp = exp_table[exp_i];

          for (sign = 0; sign >= -1; sign--)
            {
              got = mpn_get_d (np, nsize, sign, exp);
              if (got != 0.0)
                {
                  printf  ("mpn_get_d wrong, didn't get 0.0 on underflow\n");
                  printf  ("  nsize    %ld\n", (long) nsize);
                  printf  ("  exp      %ld\n", exp);
                  printf  ("  sign     %ld\n", (long) sign);
                  d_trace ("  got      ", got);
                  abort ();
                }
            }
        }
    }
  else
    {
      printf ("Warning, underflow to zero tests skipped due to SIGFPE (exp=%ld)\n", exp);
    }
  tests_sigfpe_done ();
}


/* Expect large values to result in +/-inf, on IEEE systems. */
void
check_inf (void)
{
  static const long exp_table[] = {
    999999L, LONG_MAX,
  };
  static const mp_limb_t  np[4] = { 1, 1, 1, 1 };
  long       exp;
  mp_size_t  nsize, sign, got_sign;
  double     got;
  int        exp_i;

  if (! _GMP_IEEE_FLOATS)
    return;

  for (nsize = 1; nsize <= numberof (np); nsize++)
    {
      for (exp_i = 0; exp_i < numberof (exp_table); exp_i++)
        {
          exp = exp_table[exp_i];

          for (sign = 0; sign >= -1; sign--)
            {
              got = mpn_get_d (np, nsize, sign, exp);
              got_sign = (got >= 0 ? 0 : -1);
              if (! tests_isinf (got))
                {
                  printf  ("mpn_get_d wrong, didn't get infinity\n");
                bad:
                  printf  ("  nsize    %ld\n", (long) nsize);
                  printf  ("  exp      %ld\n", exp);
                  printf  ("  sign     %ld\n", (long) sign);
                  d_trace ("  got      ", got);
                  printf  ("  got sign %ld\n", (long) got_sign);
                  abort ();
                }
              if (got_sign != sign)
                {
                  printf  ("mpn_get_d wrong sign on infinity\n");
                  goto bad;
                }
            }
        }
    }
}

/* Check values 2^n approaching and into IEEE denorm range.
   Some systems might not support denorms, or might have traps setup, so
   watch out for SIGFPE.  */
void
check_ieee_denorm (void)
{
  static long exp;
  mp_limb_t  n = 1;
  long       i;
  mp_size_t  sign;
  double     want, got;

  if (! _GMP_IEEE_FLOATS)
    return;

  if (tests_setjmp_sigfpe() == 0)
    {
      exp = -1020;
      want = 1.0;
      for (i = 0; i > exp; i--)
        want *= 0.5;

      for ( ; exp > -1500 && want != 0.0; exp--)
        {
          for (sign = 0; sign >= -1; sign--)
            {
              got = mpn_get_d (&n, (mp_size_t) 1, sign, exp);
              if (got != want)
                {
                  printf  ("mpn_get_d wrong on denorm\n");
                  printf  ("  n=1\n");
                  printf  ("  exp   %ld\n", exp);
                  printf  ("  sign  %ld\n", (long) sign);
                  d_trace ("  got   ", got);
                  d_trace ("  want  ", want);
                  abort ();
                }
              want = -want;
            }
          want *= 0.5;
          FORCE_DOUBLE (want);
        }
    }
  else
    {
      printf ("Warning, IEEE denorm tests skipped due to SIGFPE (exp=%ld)\n", exp);
    }
  tests_sigfpe_done ();
}


/* Check values 2^n approaching exponent overflow.
   Some systems might trap on overflow, so watch out for SIGFPE.  */
void
check_ieee_overflow (void)
{
  static long exp;
  mp_limb_t  n = 1;
  long       i;
  mp_size_t  sign;
  double     want, got;

  if (! _GMP_IEEE_FLOATS)
    return;

  if (tests_setjmp_sigfpe() == 0)
    {
      exp = 1010;
      want = 1.0;
      for (i = 0; i < exp; i++)
        want *= 2.0;

      for ( ; exp < 1050; exp++)
        {
          for (sign = 0; sign >= -1; sign--)
            {
              got = mpn_get_d (&n, (mp_size_t) 1, sign, exp);
              if (got != want)
                {
                  printf  ("mpn_get_d wrong on overflow\n");
                  printf  ("  n=1\n");
                  printf  ("  exp   %ld\n", exp);
                  printf  ("  sign  %ld\n", (long) sign);
                  d_trace ("  got   ", got);
                  d_trace ("  want  ", want);
                  abort ();
                }
              want = -want;
            }
          want *= 2.0;
          FORCE_DOUBLE (want);
        }
    }
  else
    {
      printf ("Warning, IEEE overflow tests skipped due to SIGFPE (exp=%ld)\n", exp);
    }
  tests_sigfpe_done ();
}


/* ARM gcc 2.95.4 was seen generating bad code for ulong->double
   conversions, resulting in for instance 0x81c25113 incorrectly converted.
   This test exercises that value, to see mpn_get_d has avoided the
   problem.  */
void
check_0x81c25113 (void)
{
#if GMP_NUMB_BITS >= 32
  double     want = 2176995603.0;
  double     got;
  mp_limb_t  np[4];
  mp_size_t  nsize;
  long       exp;

  if (tests_dbl_mant_bits() < 32)
    return;

  for (nsize = 1; nsize <= numberof (np); nsize++)
    {
      refmpn_zero (np, nsize-1);
      np[nsize-1] = CNST_LIMB(0x81c25113);
      exp = - (nsize-1) * GMP_NUMB_BITS;
      got = mpn_get_d (np, nsize, (mp_size_t) 0, exp);
      if (got != want)
        {
          printf  ("mpn_get_d wrong on 2176995603 (0x81c25113)\n");
          printf  ("  nsize  %ld\n", (long) nsize);
          printf  ("  exp    %ld\n", exp);
          d_trace ("  got    ", got);
          d_trace ("  want   ", want);
          abort ();
        }
    }
#endif
}


void
check_rand (void)
{
  gmp_randstate_ptr rands = RANDS;
  int            rep, i;
  unsigned long  mant_bits;
  long           exp, exp_min, exp_max;
  double         got, want, d;
  mp_size_t      nalloc, nsize, sign;
  mp_limb_t      nhigh_mask;
  mp_ptr         np;

  mant_bits = tests_dbl_mant_bits ();
  if (mant_bits == 0)
    return;

  /* Allow for vax D format with exponent 127 to -128 only.
     FIXME: Do something to probe for a valid exponent range.  */
  exp_min = -100 - mant_bits;
  exp_max =  100 - mant_bits;

  /* space for mant_bits */
  nalloc = BITS_TO_LIMBS (mant_bits);
  np = refmpn_malloc_limbs (nalloc);
  nhigh_mask = MP_LIMB_T_MAX
    >> (GMP_NAIL_BITS + nalloc * GMP_NUMB_BITS - mant_bits);

  for (rep = 0; rep < 200; rep++)
    {
      /* random exp_min to exp_max, inclusive */
      exp = exp_min + (long) gmp_urandomm_ui (rands, exp_max - exp_min + 1);

      /* mant_bits worth of random at np */
      if (rep & 1)
        mpn_random (np, nalloc);
      else
        mpn_random2 (np, nalloc);
      nsize = nalloc;
      np[nsize-1] &= nhigh_mask;
      MPN_NORMALIZE (np, nsize);
      if (nsize == 0)
        continue;

      sign = (mp_size_t) gmp_urandomb_ui (rands, 1L) - 1;

      /* want = {np,nsize}, converting one bit at a time */
      want = 0.0;
      for (i = 0, d = 1.0; i < mant_bits; i++, d *= 2.0)
        if (np[i/GMP_NUMB_BITS] & (CNST_LIMB(1) << (i%GMP_NUMB_BITS)))
          want += d;
      if (sign < 0)
        want = -want;

      /* want = want * 2^exp */
      for (i = 0; i < exp; i++)
        want *= 2.0;
      for (i = 0; i > exp; i--)
        want *= 0.5;

      got = mpn_get_d (np, nsize, sign, exp);

      if (got != want)
        {
          printf    ("mpn_get_d wrong on random data\n");
          printf    ("   sign     %ld\n", (long) sign);
          mpn_trace ("   n        ", np, nsize);
          printf    ("   nsize    %ld\n", (long) nsize);
          printf    ("   exp      %ld\n", exp);
          d_trace   ("   want     ", want);
          d_trace   ("   got      ", got);
          abort();
        }
    }

  free (np);
}


int
main (void)
{
  tests_start ();
  mp_trace_base = -16;

  check_onebit ();
  check_twobit ();
  check_inf ();
  check_underflow ();
  check_ieee_denorm ();
  check_ieee_overflow ();
  check_0x81c25113 ();
#if ! (defined (__vax) || defined (__vax__))
  check_rand ();
#endif

  tests_end ();
  exit (0);
}
