/* Test file for mpfr_fmma and mpfr_fmms.

Copyright 2016-2018 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
http://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"

/* check both mpfr_fmma and mpfr_fmms */
static void
random_test (mpfr_t a, mpfr_t b, mpfr_t c, mpfr_t d, mpfr_rnd_t rnd)
{
  mpfr_t ref, res, ab, cd;
  int inex_ref, inex_res;
  mpfr_prec_t p = MPFR_PREC(a);

  mpfr_init2 (res, p);
  mpfr_init2 (ref, p);
  mpfr_init2 (ab, mpfr_get_prec (a) +  mpfr_get_prec (b));
  mpfr_init2 (cd, mpfr_get_prec (c) +  mpfr_get_prec (d));

  /* first check fmma */
  inex_res = mpfr_fmma (res, a, b, c, d, rnd);
  mpfr_mul (ab, a, b, rnd);
  mpfr_mul (cd, c, d, rnd);
  inex_ref = mpfr_add (ref, ab, cd, rnd);
  if (! SAME_SIGN (inex_res, inex_ref) ||
      mpfr_nan_p (res) || mpfr_nan_p (ref) ||
      ! mpfr_equal_p (res, ref))
    {
      printf ("mpfr_fmma failed for p=%ld rnd=%s\n",
              (long int) p, mpfr_print_rnd_mode (rnd));
      printf ("a="); mpfr_dump (a);
      printf ("b="); mpfr_dump (b);
      printf ("c="); mpfr_dump (c);
      printf ("d="); mpfr_dump (d);
      printf ("Expected\n  ");
      mpfr_dump (ref);
      printf ("  with inex = %d\n", inex_ref);
      printf ("Got\n  ");
      mpfr_dump (res);
      printf ("  with inex = %d\n", inex_res);
      exit (1);
    }

  /* then check fmms */
  inex_res = mpfr_fmms (res, a, b, c, d, rnd);
  mpfr_mul (ab, a, b, rnd);
  mpfr_mul (cd, c, d, rnd);
  inex_ref = mpfr_sub (ref, ab, cd, rnd);
  if (! SAME_SIGN (inex_res, inex_ref) ||
      mpfr_nan_p (res) || mpfr_nan_p (ref) ||
      ! mpfr_equal_p (res, ref))
    {
      printf ("mpfr_fmms failed for p=%ld rnd=%s\n",
              (long int) p, mpfr_print_rnd_mode (rnd));
      printf ("a="); mpfr_dump (a);
      printf ("b="); mpfr_dump (b);
      printf ("c="); mpfr_dump (c);
      printf ("d="); mpfr_dump (d);
      printf ("Expected\n  ");
      mpfr_dump (ref);
      printf ("  with inex = %d\n", inex_ref);
      printf ("Got\n  ");
      mpfr_dump (res);
      printf ("  with inex = %d\n", inex_res);
      exit (1);
    }

  mpfr_clear (ab);
  mpfr_clear (cd);
  mpfr_clear (res);
  mpfr_clear (ref);
}

static void
random_tests (void)
{
  mpfr_prec_t p;
  int r;
  mpfr_t a, b, c, d;

  for (p = MPFR_PREC_MIN; p <= 4096; p++)
    {
      mpfr_inits2 (p, a, b, c, d, (mpfr_ptr) 0);
      mpfr_urandomb (a, RANDS);
      mpfr_urandomb (b, RANDS);
      mpfr_urandomb (c, RANDS);
      mpfr_urandomb (d, RANDS);
      RND_LOOP_NO_RNDF (r)
        random_test (a, b, c, d, (mpfr_rnd_t) r);
      mpfr_clears (a, b, c, d, (mpfr_ptr) 0);
    }
}

static void
zero_tests (void)
{
  mpfr_exp_t emin, emax;
  mpfr_t a, b, c, d, res;
  int i, r;

  emin = mpfr_get_emin ();
  emax = mpfr_get_emax ();
  set_emin (MPFR_EMIN_MIN);
  set_emax (MPFR_EMAX_MAX);

  mpfr_inits2 (GMP_NUMB_BITS, a, b, c, d, (mpfr_ptr) 0);
  for (i = 0; i <= 4; i++)
    {
      switch (i)
        {
        case 0: case 1:
          mpfr_set_ui (a, i, MPFR_RNDN);
          break;
        case 2:
          mpfr_setmax (a, MPFR_EMAX_MAX);
          break;
        case 3:
          mpfr_setmin (a, MPFR_EMIN_MIN);
          break;
        case 4:
          mpfr_setmin (a, MPFR_EMIN_MIN+1);
          break;
        }
      RND_LOOP (r)
        {
          int j, inex;
          mpfr_flags_t flags;

          mpfr_set (b, a, MPFR_RNDN);
          mpfr_set (c, a, MPFR_RNDN);
          mpfr_neg (d, a, MPFR_RNDN);
          /* We also want to test cases where the precision of the
             result is twice the precision of each input, in case
             add1sp.c and/or sub1sp.c could be involved. */
          for (j = 1; j <= 2; j++)
            {
              mpfr_init2 (res, GMP_NUMB_BITS * j);
              mpfr_clear_flags ();
              inex = mpfr_fmma (res, a, b, c, d, (mpfr_rnd_t) r);
              flags = __gmpfr_flags;
              if (flags != 0 || inex != 0 || ! mpfr_zero_p (res) ||
                  (r == MPFR_RNDD ? MPFR_IS_POS (res) : MPFR_IS_NEG (res)))
                {
                  printf ("Error in zero_tests on i = %d, %s\n",
                          i, mpfr_print_rnd_mode ((mpfr_rnd_t) r));
                  printf ("Expected %c0, inex = 0\n",
                          r == MPFR_RNDD ? '-' : '+');
                  printf ("Got      ");
                  if (MPFR_IS_POS (res))
                    printf ("+");
                  mpfr_out_str (stdout, 16, 0, res, MPFR_RNDN);
                  printf (", inex = %d\n", inex);
                  printf ("Expected flags:");
                  flags_out (0);
                  printf ("Got flags:     ");
                  flags_out (flags);
                  exit (1);
                }
              mpfr_clear (res);
            } /* j */
        } /* r */
    } /* i */
  mpfr_clears (a, b, c, d, (mpfr_ptr) 0);

  set_emin (emin);
  set_emax (emax);
}

static void
max_tests (void)
{
  mpfr_exp_t emin, emax;
  mpfr_t x, y1, y2;
  int r;
  int i, inex1, inex2;
  mpfr_flags_t flags1, flags2;

  emin = mpfr_get_emin ();
  emax = mpfr_get_emax ();
  set_emin (MPFR_EMIN_MIN);
  set_emax (MPFR_EMAX_MAX);

  mpfr_init2 (x, GMP_NUMB_BITS);
  mpfr_setmax (x, MPFR_EMAX_MAX);
  flags1 = MPFR_FLAGS_OVERFLOW | MPFR_FLAGS_INEXACT;
  RND_LOOP (r)
    {
      /* We also want to test cases where the precision of the
         result is twice the precision of each input, in case
         add1sp.c and/or sub1sp.c could be involved. */
      for (i = 1; i <= 2; i++)
        {
          mpfr_inits2 (GMP_NUMB_BITS * i, y1, y2, (mpfr_ptr) 0);
          inex1 = mpfr_mul (y1, x, x, (mpfr_rnd_t) r);
          mpfr_clear_flags ();
          inex2 = mpfr_fmma (y2, x, x, x, x, (mpfr_rnd_t) r);
          flags2 = __gmpfr_flags;
          if (! (flags1 == flags2 && SAME_SIGN (inex1, inex2) &&
                 mpfr_equal_p (y1, y2)))
            {
              printf ("Error in max_tests for %s\n",
                      mpfr_print_rnd_mode ((mpfr_rnd_t) r));
              printf ("Expected ");
              mpfr_dump (y1);
              printf ("  with inex = %d, flags =", inex1);
              flags_out (flags1);
              printf ("Got      ");
              mpfr_dump (y2);
              printf ("  with inex = %d, flags =", inex2);
              flags_out (flags2);
              exit (1);
            }
          mpfr_clears (y1, y2, (mpfr_ptr) 0);
        } /* i */
    } /* r */
  mpfr_clear (x);

  set_emin (emin);
  set_emax (emax);
}

/* a^2 - (a+k)(a-k) = k^2 where a^2 overflows but k^2 usually doesn't.
 * a^2 + cd where a^2 overflows and cd doesn't affect the overflow
 * (and cd may even underflow).
 */
static void
overflow_tests (void)
{
  mpfr_exp_t old_emax;
  int i;

  old_emax = mpfr_get_emax ();

  for (i = 0; i < 40; i++)
    {
      mpfr_exp_t emax, exp_a;
      mpfr_t a, k, c, d, z1, z2;
      mpfr_rnd_t rnd;
      mpfr_prec_t prec_a, prec_z;
      int add = i & 1, inex, inex1, inex2;
      mpfr_flags_t flags1, flags2;

      /* In most cases, we do the test with the maximum exponent. */
      emax = i % 8 != 0 ? MPFR_EMAX_MAX : 64 + (randlimb () % 1);
      set_emax (emax);
      exp_a = emax/2 + 32;

      rnd = RND_RAND_NO_RNDF ();
      prec_a = 64 + randlimb () % 100;
      prec_z = MPFR_PREC_MIN + randlimb () % 160;

      mpfr_init2 (a, prec_a);
      mpfr_urandom (a, RANDS, MPFR_RNDU);
      mpfr_set_exp (a, exp_a);

      mpfr_init2 (k, prec_a - 32);
      mpfr_urandom (k, RANDS, MPFR_RNDU);
      mpfr_set_exp (k, exp_a - 32);

      mpfr_init2 (c, prec_a + 1);
      inex = mpfr_add (c, a, k, MPFR_RNDN);
      MPFR_ASSERTN (inex == 0);

      mpfr_init2 (d, prec_a);
      inex = mpfr_sub (d, a, k, MPFR_RNDN);
      MPFR_ASSERTN (inex == 0);
      if (add)
        mpfr_neg (d, d, MPFR_RNDN);

      mpfr_init2 (z1, prec_z);
      mpfr_clear_flags ();
      inex1 = mpfr_sqr (z1, k, rnd);
      flags1 = __gmpfr_flags;

      mpfr_init2 (z2, prec_z);
      mpfr_clear_flags ();
      inex2 = add ?
        mpfr_fmma (z2, a, a, c, d, rnd) :
        mpfr_fmms (z2, a, a, c, d, rnd);
      flags2 = __gmpfr_flags;

      if (! (flags1 == flags2 && SAME_SIGN (inex1, inex2) &&
             mpfr_equal_p (z1, z2)))
        {
          printf ("Error 1 in overflow_tests for %s\n",
                  mpfr_print_rnd_mode (rnd));
          printf ("Expected ");
          mpfr_dump (z1);
          printf ("  with inex = %d, flags =", inex1);
          flags_out (flags1);
          printf ("Got      ");
          mpfr_dump (z2);
          printf ("  with inex = %d, flags =", inex2);
          flags_out (flags2);
          exit (1);
        }

      /* c and d such that a^2 +/- cd ~= a^2 (overflow) */
      mpfr_urandom (c, RANDS, MPFR_RNDU);
      mpfr_set_exp (c, randlimb () % 1 ? exp_a - 2 : __gmpfr_emin);
      if (randlimb () % 1)
        mpfr_neg (c, c, MPFR_RNDN);
      mpfr_urandom (d, RANDS, MPFR_RNDU);
      mpfr_set_exp (d, randlimb () % 1 ? exp_a - 2 : __gmpfr_emin);
      if (randlimb () % 1)
        mpfr_neg (d, d, MPFR_RNDN);

      mpfr_clear_flags ();
      inex1 = mpfr_sqr (z1, a, rnd);
      flags1 = __gmpfr_flags;
      MPFR_ASSERTN (flags1 == (MPFR_FLAGS_OVERFLOW | MPFR_FLAGS_INEXACT));

      mpfr_clear_flags ();
      inex2 = add ?
        mpfr_fmma (z2, a, a, c, d, rnd) :
        mpfr_fmms (z2, a, a, c, d, rnd);
      flags2 = __gmpfr_flags;

      if (! (flags1 == flags2 && SAME_SIGN (inex1, inex2) &&
             mpfr_equal_p (z1, z2)))
        {
          printf ("Error 2 in overflow_tests for %s\n",
                  mpfr_print_rnd_mode (rnd));
          printf ("Expected ");
          mpfr_dump (z1);
          printf ("  with inex = %d, flags =", inex1);
          flags_out (flags1);
          printf ("Got      ");
          mpfr_dump (z2);
          printf ("  with inex = %d, flags =", inex2);
          flags_out (flags2);
          printf ("a="); mpfr_dump (a);
          printf ("c="); mpfr_dump (c);
          printf ("d="); mpfr_dump (d);
          exit (1);
        }

      mpfr_clears (a, k, c, d, z1, z2, (mpfr_ptr) 0);
    }

  set_emax (old_emax);
}

/* (1/2)x + (1/2)x = x tested near underflow */
static void
half_plus_half (void)
{
  mpfr_exp_t emin;
  mpfr_t h, x1, x2, y;
  int neg, r, i, inex;
  mpfr_flags_t flags;

  emin = mpfr_get_emin ();
  set_emin (MPFR_EMIN_MIN);
  mpfr_inits2 (4, h, x1, (mpfr_ptr) 0);
  mpfr_init2 (x2, GMP_NUMB_BITS);
  mpfr_set_ui_2exp (h, 1, -1, MPFR_RNDN);

  for (mpfr_setmin (x1, __gmpfr_emin);
       MPFR_GET_EXP (x1) < __gmpfr_emin + 2;
       mpfr_nextabove (x1))
    {
      inex = mpfr_set (x2, x1, MPFR_RNDN);
      MPFR_ASSERTN (inex == 0);
      for (neg = 0; neg < 2; neg++)
        {
          RND_LOOP (r)
            {
              /* We also want to test cases where the precision of the
                 result is twice the precision of each input, in case
                 add1sp.c and/or sub1sp.c could be involved. */
              for (i = 1; i <= 2; i++)
                {
                  mpfr_init2 (y, GMP_NUMB_BITS * i);
                  mpfr_clear_flags ();
                  inex = mpfr_fmma (y, h, x2, h, x2, (mpfr_rnd_t) r);
                  flags = __gmpfr_flags;
                  if (! (flags == 0 && inex == 0 && mpfr_equal_p (y, x2)))
                    {
                      printf ("Error in half_plus_half for %s\n",
                              mpfr_print_rnd_mode ((mpfr_rnd_t) r));
                      printf ("Expected ");
                      mpfr_dump (x2);
                      printf ("  with inex = 0, flags =");
                      flags_out (0);
                      printf ("Got      ");
                      mpfr_dump (y);
                      printf ("  with inex = %d, flags =", inex);
                      flags_out (flags);
                      exit (1);
                    }
                  mpfr_clear (y);
                }
            }
          mpfr_neg (x2, x2, MPFR_RNDN);
        }
    }

  mpfr_clears (h, x1, x2, (mpfr_ptr) 0);
  set_emin (emin);
}

/* check that result has exponent in [emin, emax]
   (see https://sympa.inria.fr/sympa/arc/mpfr/2017-04/msg00016.html)
   Overflow detection in sub1.c was incorrect (only for UBF cases);
   fixed in r11414. */
static void
bug20170405 (void)
{
  mpfr_t x, y, z;

  mpfr_inits2 (866, x, y, z, (mpfr_ptr) 0);

  mpfr_set_str_binary (x, "-0.10010101110110001111000110010100011001111011110001010100010000111110001010111110100001000000011010001000010000101110000000001100001011000110010000100111001100000101110000000001001101101101010110000110100010010111011001101101010011111000101100000010001100010000011100000000011110100010111011101011000101101011110110001010011001101110011101100001111000011000000011000010101010000101001001010000111101100001000001011110011000110010001100001101101001001010000111100101000010111001001101010011001110110001000010101001100000101010110000100100100010101011111001001100010001010110011000000001011110011000110001000100101000111010010111111110010111001101110101010010101101010100111001011100101101010111010011001000001010011001010001101000111011010010100110011001111111000011101111001010111001001011011011110101101001100011010001010110011100001101100100001001100111010100010100E768635654");
  mpfr_set_str_binary (y, "-0.11010001010111110010110101010011000010010011010011011101100100110000110101100110111010001001110101110000011101100010110100100011001101111010100011111001011100111101110101101001000101011110101101101011010100110010111111010011011100101111110011001001010101011101111100011101100001010010011000110010110101001110010001101111111001100100000101010100110011101101101010011001000110100001001100000010110010101111000110110000111011000110001000100100100101111110001111100101011100100100110111010000010110110001110010001001101000000110111000101000110101111110000110001110100010101111010110001111010111111111010011001001100110011000110010110011000101110001010001101000100010000110011101010010010011110100000111100000101100110001111010000100011111000001101111110100000011011110010100010010011111111000010110000000011010011001100110001110111111010111110000111110010110011001000010E768635576");
  /* since emax = 1073741821, x^2-y^2 should overflow */
  mpfr_fmms (z, x, x, y, y, MPFR_RNDN);
  MPFR_ASSERTN(mpfr_inf_p (z) && mpfr_sgn (z) > 0);

  /* same test when z has a different precision */
  mpfr_set_prec (z, 867);
  mpfr_fmms (z, x, x, y, y, MPFR_RNDN);
  MPFR_ASSERTN(mpfr_inf_p (z) && mpfr_sgn (z) > 0);

  mpfr_set_prec (x, 564);
  mpfr_set_prec (y, 564);
  mpfr_set_prec (z, 2256);
  mpfr_set_str_binary (x, "1.10010000111100110011001101111111101000111001011000110100110010000101000100010001000000111100010000101001011011111001111000110101111100101111001100001100011101100100011110000000011000010110111100111000100101010001011111010111011001110010001011101111001011001110110000010000011100010001010001011100100110111110101001001111001011101111110011101110101010110100010010111011111100010101111100011110111001011111101110101101101110100101111010000101011110100000000110111101000001100001000100010110100111010011011010110011100111010000101110010101111001011100110101100001100e-737194993");
  mpfr_set_str_binary (y, "-1.00101000100001001101011110100010110011101010011011010111100110101011111100000100101000111010111101100100110010001110011011100100110110000001011001000111101111101111110101100110111000000011000001101001010100010010001110001000011010000100111001001100101111111100010101110101001101101101111010100011011110001000010000010100011000011000010110101100000111111110111001100100100001101011111011100101110111000100101010110100010011101010110010100110100111000000100111101101101000000011110000100110100100011000010011110010001010000110100011111101101101110001110001101101010e-737194903");
  mpfr_fmma (z, x, y, x, y, MPFR_RNDN);
  /* we should get -0 as result */
  MPFR_ASSERTN(mpfr_zero_p (z) && mpfr_signbit (z));

  mpfr_set_prec (x, 2);
  mpfr_set_prec (y, 2);
  mpfr_set_prec (z, 2);
  /* (a+i*b)*(c+i*d) with:
     a=0.10E1
     b=0.10E-536870912
     c=0.10E-536870912
     d=0.10E1 */
  mpfr_set_str_binary (x, "0.10E1"); /* x = a = d */
  mpfr_set_str_binary (y, "0.10E-536870912"); /* y = b = c */
  /* real part is a*c-b*d = x*y-y*x */
  mpfr_fmms (z, x, y, y, x, MPFR_RNDN);
  MPFR_ASSERTN(mpfr_zero_p (z) && !mpfr_signbit (z));
  /* imaginary part is a*d+b*c = x*x+y*y */
  mpfr_fmma (z, x, x, y, y, MPFR_RNDN);
  MPFR_ASSERTN(mpfr_cmp_ui (z, 1) == 0);

  mpfr_clears (x, y, z, (mpfr_ptr) 0);
}

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

  random_tests ();
  zero_tests ();
  max_tests ();
  overflow_tests ();
  half_plus_half ();
  bug20170405 ();

  tests_end_mpfr ();
  return 0;
}
