/* Test file for mpfr_gamma_inc

Copyright 2016-2022 Free Software Foundation, Inc.
Contributed by the AriC and Caramba projects, INRIA.

This file is part of the GNU MPFR Library.

The GNU MPFR Library 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.

The GNU MPFR Library 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 the GNU MPFR Library; see the file COPYING.LESSER.  If not, see
https://www.gnu.org/licenses/ or write to the Free Software Foundation, Inc.,
51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */

#include "mpfr-test.h"

#define TEST_FUNCTION mpfr_gamma_inc
#define TWO_ARGS
#define TEST_RANDOM_POS2 0 /* the 2nd argument is never negative */
#define TGENERIC_NOWARNING 1
#define TEST_RANDOM_EMAX 8
#define TEST_RANDOM_EMIN -32
#define REDUCE_EMAX TEST_RANDOM_EMAX
#define REDUCE_EMIN TEST_RANDOM_EMIN
#include "tgeneric.c"

/* do k random tests at precision p */
static void
test_random (mpfr_prec_t p, int k)
{
  mpfr_t a, x, y, z, t;

  mpfr_inits2 (p, a, x, y, z, (mpfr_ptr) 0);
  mpfr_init2 (t, p + 20);
  while (k--)
    {
      do mpfr_urandomb (a, RANDS); while (mpfr_zero_p (a));
      if (randlimb () & 1)
        mpfr_neg (a, a, MPFR_RNDN);
      do mpfr_urandomb (x, RANDS); while (mpfr_zero_p (x));
      mpfr_gamma_inc (y, a, x, MPFR_RNDN);
      mpfr_gamma_inc (t, a, x, MPFR_RNDN);
      if (mpfr_can_round (t, mpfr_get_prec (z), MPFR_RNDN, MPFR_RNDN, p))
        {
          mpfr_set (z, t, MPFR_RNDN);
          if (mpfr_cmp (y, z))
            {
              printf ("mpfr_gamma_inc failed for a=");
              mpfr_out_str (stdout, 10, 0, a, MPFR_RNDN);
              printf (" x=");
              mpfr_out_str (stdout, 10, 0, x, MPFR_RNDN);
              printf ("\nexpected ");
              mpfr_out_str (stdout, 10, 0, z, MPFR_RNDN);
              printf ("\ngot      ");
              mpfr_out_str (stdout, 10, 0, y, MPFR_RNDN);
              printf ("\n");
              exit (1);
            }
        }
    }
  mpfr_clears (a, x, y, z, (mpfr_ptr) 0);
  mpfr_clear (t);
}

static void
specials (void)
{
  mpfr_t a, x;
  int inex;

  mpfr_init2 (a, 2);
  mpfr_init2 (x, 2);

  mpfr_set_inf (a, 1);
  mpfr_set_inf (x, 1);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_nan_p (a));

  mpfr_set_inf (a, 1);
  mpfr_set_inf (x, -1);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_nan_p (a));

  mpfr_set_inf (a, -1);
  mpfr_set_inf (x, -1);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_nan_p (a));

  mpfr_set_inf (a, -1);
  mpfr_set_inf (x, 1);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_zero_p (a) || mpfr_signbit (a) == 0);

  mpfr_set_inf (a, 1);
  mpfr_set_ui (x, 1, MPFR_RNDN);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_inf_p (a) || mpfr_signbit (a) == 0);

  mpfr_set_inf (a, -1);
  mpfr_set_ui (x, 2, MPFR_RNDN);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_zero_p (a) || mpfr_signbit (a) == 0);

  mpfr_set_inf (a, -1);
  mpfr_set_ui (x, 1, MPFR_RNDN);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_zero_p (a) || mpfr_signbit (a) == 0);

  mpfr_set_inf (a, -1);
  mpfr_set_ui_2exp (x, 1, -1, MPFR_RNDN);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_inf_p (a) || mpfr_signbit (a) == 0);

  mpfr_set_ui (a, 1, MPFR_RNDN);
  mpfr_set_inf (x, 1);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_zero_p (a) || mpfr_signbit (a) == 0);

  /* gamma_inc(1,-x) = exp(x) tends to +Inf */
  mpfr_set_ui (a, 1, MPFR_RNDN);
  mpfr_set_inf (x, -1);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_inf_p (a) || mpfr_signbit (a) == 0);

  /* gamma_inc(0,x) for x < 0 has imaginary part -Pi and thus gives NaN
     over the reals */
  mpfr_set_zero (a, 1);
  mpfr_set_inf (x, -1);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_nan_p (a));

  /* gamma_inc(-1,x) for x < 0 has imaginary part +Pi and thus gives NaN */
  mpfr_set_si (a, -1, MPFR_RNDN);
  mpfr_set_inf (x, -1);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_nan_p (a));

  /* gamma_inc(-2,x) for x < 0 has imaginary part -Pi/2 and thus gives NaN */
  mpfr_set_si (a, -2, MPFR_RNDN);
  mpfr_set_inf (x, -1);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_nan_p (a));

  mpfr_set_ui_2exp (a, 1, -1, MPFR_RNDN);
  mpfr_set_inf (x, -1);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_nan_p (a));

  mpfr_set_si_2exp (a, -1, -1, MPFR_RNDN);
  mpfr_set_inf (x, -1);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_nan_p (a));

  /* gamma_inc(0,x) = -Ei(-x) */
  mpfr_set_zero (a, 1);
  mpfr_set_si (x, -1, MPFR_RNDN);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_nan_p (a));

  /* gamma_inc(a,0) = gamma(a) thus gamma_inc(-Inf,0) = gamma(-Inf) = NaN */
  mpfr_set_inf (a, -1);
  mpfr_set_zero (x, 1);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_nan_p (a));

  mpfr_set_inf (a, -1);
  mpfr_set_si (x, -1, MPFR_RNDN);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  MPFR_ASSERTN (mpfr_nan_p (a));

  /* check gamma_inc(2,0) = 1 is exact */
  mpfr_set_ui (a, 2, MPFR_RNDN);
  mpfr_set_ui (x, 0, MPFR_RNDN);
  mpfr_clear_inexflag ();
  inex = mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  if (mpfr_cmp_ui (a, 1))
    {
      printf ("Error for gamma_inc(2,0)\n");
      printf ("expected 1\n");
      printf ("got      ");
      mpfr_out_str (stdout, 10, 0, a, MPFR_RNDN);
      printf ("\n");
      exit (1);
    }
  if (inex != 0)
    {
      printf ("Wrong ternary value for gamma_inc(2,0)\n");
      printf ("expected 0\n");
      printf ("got      %d\n", inex);
      exit (1);
    }
  if (mpfr_inexflag_p ())
    {
      printf ("Wrong inexact flag for gamma_inc(2,0)\n");
      printf ("expected 0\n");
      printf ("got      1\n");
      exit (1);
    }

  /* check gamma_inc(0,1) = 0.219383934395520 */
  mpfr_set_ui (a, 0, MPFR_RNDN);
  mpfr_set_ui (x, 1, MPFR_RNDN);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  if (mpfr_cmp_ui_2exp (a, 1, -2))
    {
      printf ("Error for gamma_inc(0,1)\n");
      printf ("expected 0.25\n");
      printf ("got      ");
      mpfr_out_str (stdout, 10, 0, a, MPFR_RNDN);
      printf ("\n");
      exit (1);
    }

  /* check gamma_inc(-1,1) = 0.148495506775922 */
  mpfr_set_si (a, -1, MPFR_RNDN);
  mpfr_set_ui (x, 1, MPFR_RNDN);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  if (mpfr_cmp_ui_2exp (a, 1, -3))
    {
      printf ("Error for gamma_inc(-1,1)\n");
      printf ("expected 0.125\n");
      printf ("got      ");
      mpfr_out_str (stdout, 10, 0, a, MPFR_RNDN);
      printf ("\n");
      exit (1);
    }

  /* check gamma_inc(-2,1) = 0.109691967197760 */
  mpfr_set_si (a, -2, MPFR_RNDN);
  mpfr_set_ui (x, 1, MPFR_RNDN);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  if (mpfr_cmp_ui_2exp (a, 1, -3))
    {
      printf ("Error for gamma_inc(-2,1)\n");
      printf ("expected 0.125\n");
      printf ("got      ");
      mpfr_out_str (stdout, 10, 0, a, MPFR_RNDN);
      printf ("\n");
      exit (1);
    }

  /* check gamma_inc(-3,1) = 0.109691967197760 */
  mpfr_set_si (a, -3, MPFR_RNDN);
  mpfr_set_ui (x, 1, MPFR_RNDN);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  if (mpfr_cmp_ui_2exp (a, 3, -5))
    {
      printf ("Error for gamma_inc(-3,1)\n");
      printf ("expected 3/32\n");
      printf ("got      ");
      mpfr_out_str (stdout, 10, 0, a, MPFR_RNDN);
      printf ("\n");
      exit (1);
    }

  /* check gamma_inc(-100,1) = 0.00364201018241046 */
  mpfr_set_si (a, -100, MPFR_RNDN);
  mpfr_set_ui (x, 1, MPFR_RNDN);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);
  if (mpfr_cmp_ui_2exp (a, 1, -8))
    {
      printf ("Error for gamma_inc(-100,1)\n");
      printf ("expected 1/256\n");
      printf ("got      ");
      mpfr_out_str (stdout, 10, 0, a, MPFR_RNDN);
      printf ("\n");
      exit (1);
    }

  /* TODO: Once internal overflow is supported, add new tests with
     larger exponents, e.g. 64 (in addition to 25). */
  mpfr_set_prec (a, 1);
  mpfr_set_prec (x, 1);
  mpfr_set_ui_2exp (a, 1, 25, MPFR_RNDN);
  mpfr_set_ui_2exp (x, 1, -25, MPFR_RNDN);
  mpfr_gamma_inc (a, a, x, MPFR_RNDN);

  mpfr_clear (a);
  mpfr_clear (x);
}

/* tests for negative integer a: for -n <= a <= -1, perform k tests
   with random x in 0..|a| and precision 'prec' */
static void
test_negint (long n, unsigned long k, mpfr_prec_t prec)
{
  long i, j;
  mpfr_t a, x, y;

  mpfr_init2 (a, prec);
  mpfr_init2 (x, prec);
  mpfr_init2 (y, prec);
  for (i = 1; i <= n; i++)
    {
      mpfr_set_si (a, -i, MPFR_RNDN);
      for (j = 1; j <= k; j++)
        {
          mpfr_urandomb (x, RANDS);
          mpfr_mul_si (x, x, j, MPFR_RNDN);
          mpfr_set_prec (y, prec + 20);
          mpfr_gamma_inc (y, a, x, MPFR_RNDZ);
          mpfr_gamma_inc (x, a, x, MPFR_RNDZ);
          mpfr_prec_round (y, prec, MPFR_RNDZ);
          if (mpfr_cmp (x, y))
            {
              printf ("Error in mpfr_gamma_inc(%ld,%ld) with MPFR_RNDZ\n",
                      -i, j);
              printf ("expected ");
              mpfr_out_str (stdout, 10, 0, y, MPFR_RNDN);
              printf ("\ngot      ");
              mpfr_out_str (stdout, 10, 0, x, MPFR_RNDN);
              printf ("\n");
              exit (1);
            }
        }
    }
  mpfr_clear (a);
  mpfr_clear (x);
  mpfr_clear (y);
}

static void
coverage (void)
{
  mpfr_t a, x, y;
  int inex;

  mpfr_init2 (a, MPFR_PREC_MIN);
  mpfr_init2 (x, MPFR_PREC_MIN);
  mpfr_init2 (y, MPFR_PREC_MIN);

  /* exercise test MPFR_GET_EXP(a) + 3 > w in mpfr_gamma_inc_negint */
  mpfr_set_si (a, -256, MPFR_RNDN);
  mpfr_set_ui (x, 1, MPFR_RNDN);
  inex = mpfr_gamma_inc (y, a, x, MPFR_RNDN);
  /* gamma_inc(-256,1) = 0.00143141575826821 is rounded to 2^(-10) */
  MPFR_ASSERTN(inex < 0);
  MPFR_ASSERTN(mpfr_cmp_ui_2exp (y, 1, -10) == 0);

  /* exercise the case MPFR_IS_ZERO(s) in mpfr_gamma_inc_negint */
  mpfr_set_prec (a, 4);
  mpfr_set_prec (x, 4);
  mpfr_set_prec (y, 4);
  inex = mpfr_set_si (a, -15, MPFR_RNDN);
  MPFR_ASSERTN (inex == 0);
  inex = mpfr_set_ui (x, 15, MPFR_RNDN);
  MPFR_ASSERTN (inex == 0);
  /* gamma_inc(-15,15) = 0.2290433491e-25, rounded to 14*2^(-89) */
  inex = mpfr_gamma_inc (y, a, x, MPFR_RNDN);
  MPFR_ASSERTN(inex < 0);
  MPFR_ASSERTN(mpfr_cmp_ui_2exp (y, 14, -89) == 0);

  mpfr_clear (a);
  mpfr_clear (x);
  mpfr_clear (y);
}

int
main (int argc, char *argv[])
{
  mpfr_prec_t p;

  tests_start_mpfr ();

  if (argc == 4) /* tgamma_inc a x prec: print gamma_inc(a,x) to prec bits */
    {
      mpfr_prec_t p = atoi (argv[3]);
      mpfr_t a, x;
      mpfr_init2 (a, p);
      mpfr_init2 (x, p);
      mpfr_set_str (a, argv[1], 10, MPFR_RNDN);
      mpfr_set_str (x, argv[2], 10, MPFR_RNDN);
      mpfr_gamma_inc (x, a, x, MPFR_RNDN);
      mpfr_out_str (stdout, 10, 0, x, MPFR_RNDN);
      printf ("\n");
      mpfr_clear (a);
      mpfr_clear (x);
      return 0;
    }

  coverage ();

  specials ();

  test_negint (30, 10, 53);

  for (p = MPFR_PREC_MIN; p < 100; p++)
    test_random (p, 10);

  test_generic (MPFR_PREC_MIN, 100, 5);

  tests_end_mpfr ();
  return 0;
}
