/* Test file for mpfr_frac.

Copyright 2002-2019 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 PIP 70
#define PFP 70
#define PMAX (PIP+2*PFP)

static void
check0 (mpfr_ptr ip, mpfr_ptr fp, mpfr_prec_t prec, mpfr_rnd_t rnd)
{
  mpfr_t sum, tmp, dst, fp2;
  int inex1, inex2;

  mpfr_init2 (sum, PMAX);
  mpfr_init2 (tmp, PMAX);
  mpfr_init2 (dst, prec);
  mpfr_init2 (fp2, prec);

  if (MPFR_SIGN (ip) != MPFR_SIGN (fp))
    {
      printf ("Internal error (1)\n");
      exit (1);
    }
  if (mpfr_add (sum, ip, fp, MPFR_RNDZ))
    {
      printf ("Wrong inexact flag in mpfr_add\n");
      exit (1);
    }
  if (MPFR_SIGN (sum) != MPFR_SIGN (fp))
    {
      printf ("Internal error (2)\n");
      exit (1);
    }

  inex1 = mpfr_frac (dst, sum, rnd);
  inex2 = mpfr_set (fp2, fp, rnd);
  if (inex1 != inex2)
    {
      printf ("Wrong inexact flag in mpfr_frac for\n");
      mpfr_out_str (stdout, 2, 0, sum, MPFR_RNDN);
      printf ("\nGot %d instead of %d\n", inex1, inex2);
      exit (1);
    }
  if (!mpfr_number_p (dst) ||
      MPFR_SIGN (dst) != MPFR_SIGN (fp2) ||
      mpfr_cmp (dst, fp2))
    {
      printf ("Error in mpfr_frac (y, x, %s) with\nx = ",
              mpfr_print_rnd_mode (rnd));
      mpfr_out_str (stdout, 2, 0, sum, MPFR_RNDN);
      printf ("\nGot        ");
      mpfr_out_str (stdout, 2, 0, dst, MPFR_RNDN);
      printf ("\ninstead of ");
      mpfr_out_str (stdout, 2, 0, fp2, MPFR_RNDN);
      printf ("\n");
      exit (1);
    }

  if (prec == PMAX)
    {
      inex1 = mpfr_frac (sum, sum, rnd);
      if (inex1)
        {
          printf ("Wrong inexact flag in mpfr_frac\n");
          exit (1);
        }
      if (!mpfr_number_p (sum) ||
          MPFR_SIGN (sum) != MPFR_SIGN (fp) ||
          mpfr_cmp (sum, fp))
        {
          printf ("Error in mpfr_frac (x, x, %s) with\nx = ",
                  mpfr_print_rnd_mode (rnd));
          mpfr_add (tmp, ip, fp, MPFR_RNDZ);
          mpfr_out_str (stdout, 2, 0, tmp, MPFR_RNDN);
          printf ("\nGot        ");
          mpfr_out_str (stdout, 2, 0, sum, MPFR_RNDN);
          printf ("\ninstead of ");
          mpfr_out_str (stdout, 2, 0, fp, MPFR_RNDN);
          printf ("\n");
          exit (1);
        }
    }

  mpfr_clear (fp2);
  mpfr_clear (dst);
  mpfr_clear (tmp);
  mpfr_clear (sum);
}

static void
check1 (mpfr_ptr ip, mpfr_ptr fp)
{
  int rnd;

  for (rnd = 0; rnd < MPFR_RND_MAX ; rnd++)
    {
      check0 (ip, fp, PMAX, (mpfr_rnd_t) rnd);
      check0 (ip, fp, 70, (mpfr_rnd_t) rnd);
      mpfr_neg (fp, fp, MPFR_RNDN);
      mpfr_neg (ip, ip, MPFR_RNDN);
      check0 (ip, fp, PMAX, (mpfr_rnd_t) rnd);
      check0 (ip, fp, 70, (mpfr_rnd_t) rnd);
      mpfr_neg (fp, fp, MPFR_RNDN);
      mpfr_neg (ip, ip, MPFR_RNDN);
    }
}

static void
special (void)
{
  mpfr_t z, t;

  mpfr_init (z);
  mpfr_init (t);

  mpfr_set_nan (z);
  mpfr_frac (t, z, MPFR_RNDN);
  if (!mpfr_nan_p (t))
    {
      printf ("Error for frac(NaN)\n");
      exit (1);
    }

  mpfr_set_prec (z, 6);
  mpfr_set_prec (t, 3);

  mpfr_set_str_binary (z, "0.101101E3");
  mpfr_frac (t, z, MPFR_RNDN);
  mpfr_set_str_binary (z, "0.101");
  if (mpfr_cmp (t, z))
    {
      printf ("Error in frac(0.101101E3)\n");
      exit (1);
    }

  mpfr_set_prec (z, 34);
  mpfr_set_prec (t, 26);
  mpfr_set_str_binary (z, "0.101101010000010011110011001101E9");
  mpfr_frac (t, z, MPFR_RNDN);
  mpfr_set_str_binary (z, "0.000010011110011001101");
  if (mpfr_cmp (t, z))
    {
      printf ("Error in frac(0.101101010000010011110011001101E9)\n");
      exit (1);
    }

  mpfr_clear (z);
  mpfr_clear (t);
}

static void
bug20090918 (void)
{
  mpfr_t x, y, z;
  mp_limb_t y0;
  int inexy, inexz;
  int r, i;
  const char *s[] = { "61680.352935791015625", "61680.999999" };
  mpfr_exp_t emin;

  emin = mpfr_get_emin ();
  mpfr_init2 (x, 32);
  mpfr_init2 (y, 13);

  for (i = 0; i <= 9; i++)
    {
      mpfr_set_str (x, s[i & 1], 10, MPFR_RNDZ);

      RND_LOOP(r)
        {
          set_emin ((i >> 1) - 3);
          inexy = mpfr_frac (y, x, (mpfr_rnd_t) r);
          set_emin (emin);
          y0 = MPFR_MANT(y)[0];
          while (y0 != 0 && (y0 >> 1) << 1 == y0)
            y0 >>= 1;
          if (y0 > 0x2000)
            {
              printf ("Error in bug20090918 (significand has more than"
                      " 13 bits), i = %d, %s.\n", i,
                      mpfr_print_rnd_mode ((mpfr_rnd_t) r));
              exit (1);
            }
          mpfr_init2 (z, 32);
          inexz = mpfr_frac (z, x, MPFR_RNDN);
          MPFR_ASSERTN (inexz == 0);  /* exact */
          inexz = mpfr_prec_round (z, 13, (mpfr_rnd_t) r);
          set_emin ((i >> 1) - 3);
          inexz = mpfr_check_range (z, inexz, (mpfr_rnd_t) r);
          set_emin (emin);
          if (mpfr_cmp0 (y, z) != 0)
            {
              printf ("Error in bug20090918, i = %d, %s.\n", i,
                      mpfr_print_rnd_mode ((mpfr_rnd_t) r));
              printf ("Expected ");
              mpfr_dump (z);
              printf ("Got      ");
              mpfr_dump (y);
              exit (1);
            }
          if (! SAME_SIGN (inexy, inexz))
            {
              printf ("Incorrect ternary value in bug20090918, i = %d, %s.\n",
                      i, mpfr_print_rnd_mode ((mpfr_rnd_t) r));
              printf ("Expected %d, got %d.\n", inexz, inexy);
              exit (1);
            }
          mpfr_clear (z);
        }
    }

  mpfr_clear (x);
  mpfr_clear (y);
}

#define TEST_FUNCTION mpfr_frac
#include "tgeneric.c"

int
main (void)
{
  mpfr_t ip, fp;
  int ni, nf1, nf2;

  tests_start_mpfr ();

  special ();

  mpfr_init2 (ip, PIP);
  mpfr_init2 (fp, PFP);

  for (ni = -1; ni < PIP; ni++)
    {
      if (ni <= 0)
        { /* ni + 1 */
          mpfr_set_si (ip, ni, MPFR_RNDN);
          mpfr_add_ui (ip, ip, 1, MPFR_RNDN);
        }
      else
        { /* 2^ni + 1 */
          mpfr_set_ui (ip, 1, MPFR_RNDN);
          mpfr_mul_2ui (ip, ip, ni, MPFR_RNDN);
          mpfr_add_ui (ip, ip, 1, MPFR_RNDN);
        }

      mpfr_set_ui (fp, 0, MPFR_RNDN);
      check1 (ip, fp);

      for (nf1 = 1; nf1 < PFP; nf1++)
        {
          mpfr_set_ui (fp, 1, MPFR_RNDN);
          mpfr_div_2ui (fp, fp, nf1, MPFR_RNDN);
          check1 (ip, fp);
          nf2 = 1 + (randlimb () % (PFP - 1));
          mpfr_set_ui (fp, 1, MPFR_RNDN);
          mpfr_div_2ui (fp, fp, nf2, MPFR_RNDN);
          mpfr_add_ui (fp, fp, 1, MPFR_RNDN);
          mpfr_div_2ui (fp, fp, nf1, MPFR_RNDN);
          check1 (ip, fp);
        }
    }

  mpfr_set_ui (ip, 1, MPFR_RNDN);
  mpfr_div_ui (ip, ip, 0, MPFR_RNDN);
  mpfr_set_ui (fp, 0, MPFR_RNDN);
  check1 (ip, fp);  /* test infinities */

  mpfr_clear (ip);
  mpfr_clear (fp);

  bug20090918 ();

  test_generic (MPFR_PREC_MIN, 1000, 10);

  tests_end_mpfr ();
  return 0;
}
