/* random.c -- Handle seed for random numbers.

// Copyright (C) 2008, 2009, 2010, 2011, 2012 INRIA

This file is part of GNU MPC.

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

GNU MPC 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 Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see http://www.gnu.org/licenses/ .
*/

/* Put test_start at the beginning of your test function and
   test_end at the end.
   These are an adaptation of those of MPFR. */

#include "config.h"
#include <stdlib.h>
#include "mpc-tests.h"


#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

gmp_randstate_t  rands;
static char      rands_initialized;

void
test_start (void)
{
  char *environment_seed;
  unsigned long seed;

  if (rands_initialized)
    {
      fprintf (stderr,
               "Put test_start at the beginning of your test function.\n");
      exit (1);
    }

  gmp_randinit_default (rands);
  rands_initialized = 1;

  environment_seed = getenv ("GMP_CHECK_RANDOMIZE");
  if (environment_seed == NULL)
      gmp_randseed_ui (rands, 0xfac11e);
  else
    {
      seed = (unsigned long int) atoi (environment_seed);
      if (seed == 0 || seed == 1)
        {
#if defined HAVE_GETTIMEOFDAY
          struct timeval  tv;
          gettimeofday (&tv, NULL);
          seed = (unsigned long int) (tv.tv_sec + tv.tv_usec);
#else
          time_t  tv;
          time (&tv);
          seed = (unsigned long int) tv;
#endif
          gmp_randseed_ui (rands, seed);
          printf ("Seed GMP_CHECK_RANDOMIZE=%lu "
                  "(include this in bug reports)\n", seed);
        }
      else
        {
          printf ("Re-seeding with GMP_CHECK_RANDOMIZE=%lu\n", seed);
          gmp_randseed_ui (rands, seed);
        }
    }

  /* some tests assume a given exponent range for MPFR, thus since the
     default exponent range for MPFR is not specified, we hard-code it */
  mpfr_set_emax (1073741821);
  mpfr_set_emin (-1073741821);
}

void
test_end (void)
{
  if (rands_initialized)
    {
      rands_initialized = 0;
      gmp_randclear (rands);
    }
  mpfr_free_cache ();
}

/* Set z to a non zero value random value with absolute values of Re(z) and
   Im(z) either zero (but not both in the same time) or otherwise greater than
   or equal to 2^{emin-1} and less than 2^emax.
   Each part is negative with probability equal to NEGATIVE_PROBABILITY / 256.
   The result has one zero part (but never the two of them) with probability
   equal to ZERO_PROBABILITY / 256.
*/
void
test_default_random (mpc_ptr z, mpfr_exp_t emin, mpfr_exp_t emax,
                     unsigned int negative_probability,
                     unsigned int zero_probability)
{
  const unsigned long range = (unsigned long int) (emax - emin) + 1;
  unsigned long r;

  if (!rands_initialized)
    {
      fprintf (stderr,
               "Put test_start at the beginning of your test function.\n");
      exit (1);
    }

  do
    {
      mpc_urandom (z, rands);
    } while (mpfr_zero_p (mpc_realref (z)) || mpfr_zero_p (mpc_imagref (z)));

  if (zero_probability > 256)
    zero_probability = 256;
  r = gmp_urandomb_ui (rands, 19);
  if ((r & 0x1FF) < zero_probability
      || ((r >> 9) & 0x1FF) < zero_probability)
    {
      int zero_re_p = (r & 0x1FF) < zero_probability;
      int zero_im_p = ((r >> 9) & 0x1FF) < zero_probability;

      if (zero_re_p && zero_im_p)
        {
          /* we just want one zero part. */
          zero_re_p = (r >> 18) & 1;
          zero_im_p = !zero_re_p;
        }
      if (zero_re_p)
        mpfr_set_ui (mpc_realref (z), 0, MPFR_RNDN);
      if (zero_im_p)
        mpfr_set_ui (mpc_imagref (z), 0, MPFR_RNDN);
    }
  if (!mpfr_zero_p (mpc_realref (z)))
    mpfr_set_exp (mpc_realref (z), (mpfr_exp_t) gmp_urandomm_ui (rands, range) + emin);

  if (!mpfr_zero_p (mpc_imagref (z)))
    mpfr_set_exp (mpc_imagref (z), (mpfr_exp_t) gmp_urandomm_ui (rands, range) + emin);

  if (negative_probability > 256)
    negative_probability = 256;
  r = gmp_urandomb_ui (rands, 16);
  if ((r & 0xFF) < negative_probability)
    mpfr_neg (mpc_realref (z), mpc_realref (z), MPFR_RNDN);
  if (((r>>8) & 0xFF) < negative_probability)
    mpfr_neg (mpc_imagref (z), mpc_imagref (z), MPFR_RNDN);
}

/* Set n to a non zero value random value with absolute values less than
   2^emax.

   n is negative with probability equal to NEGATIVE_PROBABILITY / 256.
*/
void test_random_si (long int *n, unsigned long emax,
                     unsigned int negative_probability)
{
  unsigned long r;

  if (!rands_initialized)
    {
      fprintf (stderr,
               "Put test_start at the beginning of your test function.\n");
      exit (1);
    }

  do
    {
      *n = gmp_urandomb_ui (rands, emax);
    } while (*n == 0);

  if (negative_probability > 256)
    negative_probability = 256;
  r = gmp_urandomb_ui (rands, 8);
  if ((r & 0xFF) < negative_probability)
    *n = -(*n);
}

/* Set x to a non zero value random value with absolute values greater than
   or equal to 2^{emin-1} and less than 2^emax.

   x is negative with probability equal to NEGATIVE_PROBABILITY / 256.
*/
void
test_random_mpfr (mpfr_ptr x, mpfr_exp_t emin, mpfr_exp_t emax,
                  unsigned int negative_probability)
{
  const unsigned long range = (unsigned long int) (emax - emin) + 1;
  unsigned long r;

  if (!rands_initialized)
    {
      fprintf (stderr,
               "Put test_start at the beginning of your test function.\n");
      exit (1);
    }

  do
    {
      mpfr_urandom (x, rands, MPFR_RNDN);
    } while (mpfr_zero_p (x));

  mpfr_set_exp (x, (mpfr_exp_t) gmp_urandomm_ui (rands, range) + emin);
  if (negative_probability > 256)
    negative_probability = 256;
  r = gmp_urandomb_ui (rands, 8);
  if ((r & 0xFF) < negative_probability)
    mpfr_neg (x, x, MPFR_RNDN);
}

/* Set x to a non zero value random value.
   x is negative with probability equal to NEGATIVE_PROBABILITY / 256.
*/
void
test_random_d (double *x, unsigned int negative_probability)
{
  MPFR_DECL_INIT (mpfr_x, 53);
  test_random_mpfr (mpfr_x, -1022, 1022, negative_probability);
  *x = mpfr_get_d (mpfr_x, MPFR_RNDN);
}

/* Set z to a non zero value random value with absolute values of Re(z) and
   Im(z) greater than or equal to 2^{emin-1} and less than 2^emax.

   Each part is negative with probability equal to NEGATIVE_PROBABILITY / 256.
*/
void
test_random_mpc (mpc_ptr z, mpfr_exp_t emin, mpfr_exp_t emax,
                 unsigned int negative_probability)
{
  const unsigned long range = (unsigned long int) (emax - emin) + 1;
  unsigned long r;

  if (!rands_initialized)
    {
      fprintf (stderr,
               "Put test_start at the beginning of your test function.\n");
      exit (1);
    }

  do
    {
      mpc_urandom (z, rands);
    } while (mpfr_zero_p (mpc_realref (z)) || mpfr_zero_p (mpc_imagref (z)));

    mpfr_set_exp (mpc_realref (z),
                  (mpfr_exp_t) gmp_urandomm_ui (rands, range) + emin);

    mpfr_set_exp (mpc_imagref (z),
                  (mpfr_exp_t) gmp_urandomm_ui (rands, range) + emin);

  if (negative_probability > 256)
    negative_probability = 256;
  r = gmp_urandomb_ui (rands, 16);
  if ((r & 0xFF) < negative_probability)
    mpfr_neg (mpc_realref (z), mpc_realref (z), MPFR_RNDN);
  if (((r>>8) & 0xFF) < negative_probability)
    mpfr_neg (mpc_imagref (z), mpc_imagref (z), MPFR_RNDN);
}
