/* __gmp_extract_double -- convert from double to array of mp_limb_t.

Copyright 1996, 1999, 2000, 2001, 2002, 2006 Free Software Foundation, Inc.

This file is part of the GNU MP Library.

The GNU MP 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 MP 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 MP Library.  If not, see http://www.gnu.org/licenses/.  */

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

#ifdef XDEBUG
#undef _GMP_IEEE_FLOATS
#endif

#ifndef _GMP_IEEE_FLOATS
#define _GMP_IEEE_FLOATS 0
#endif

#define BITS_IN_MANTISSA 53

/* Extract a non-negative double in d.  */

int
__gmp_extract_double (mp_ptr rp, double d)
{
  long exp;
  unsigned sc;
#ifdef _LONG_LONG_LIMB
#define BITS_PER_PART 64	/* somewhat bogus */
  unsigned long long int manl;
#else
#define BITS_PER_PART GMP_LIMB_BITS
  unsigned long int manh, manl;
#endif

  /* BUGS

     1. Should handle Inf and NaN in IEEE specific code.
     2. Handle Inf and NaN also in default code, to avoid hangs.
     3. Generalize to handle all GMP_LIMB_BITS >= 32.
     4. This lits is incomplete and misspelled.
   */

  ASSERT (d >= 0.0);

  if (d == 0.0)
    {
      MPN_ZERO (rp, LIMBS_PER_DOUBLE);
      return 0;
    }

#if _GMP_IEEE_FLOATS
  {
#if defined (__alpha) && __GNUC__ == 2 && __GNUC_MINOR__ == 8
    /* Work around alpha-specific bug in GCC 2.8.x.  */
    volatile
#endif
    union ieee_double_extract x;
    x.d = d;
    exp = x.s.exp;
#if BITS_PER_PART == 64		/* generalize this to BITS_PER_PART > BITS_IN_MANTISSA */
    manl = (((mp_limb_t) 1 << 63)
	    | ((mp_limb_t) x.s.manh << 43) | ((mp_limb_t) x.s.manl << 11));
    if (exp == 0)
      {
	/* Denormalized number.  Don't try to be clever about this,
	   since it is not an important case to make fast.  */
	exp = 1;
	do
	  {
	    manl = manl << 1;
	    exp--;
	  }
	while ((manl & GMP_LIMB_HIGHBIT) == 0);
      }
#endif
#if BITS_PER_PART == 32
    manh = ((mp_limb_t) 1 << 31) | (x.s.manh << 11) | (x.s.manl >> 21);
    manl = x.s.manl << 11;
    if (exp == 0)
      {
	/* Denormalized number.  Don't try to be clever about this,
	   since it is not an important case to make fast.  */
	exp = 1;
	do
	  {
	    manh = (manh << 1) | (manl >> 31);
	    manl = manl << 1;
	    exp--;
	  }
	while ((manh & GMP_LIMB_HIGHBIT) == 0);
      }
#endif
#if BITS_PER_PART != 32 && BITS_PER_PART != 64
  You need to generalize the code above to handle this.
#endif
    exp -= 1022;		/* Remove IEEE bias.  */
  }
#else
  {
    /* Unknown (or known to be non-IEEE) double format.  */
    exp = 0;
    if (d >= 1.0)
      {
	ASSERT_ALWAYS (d * 0.5 != d);

	while (d >= 32768.0)
	  {
	    d *= (1.0 / 65536.0);
	    exp += 16;
	  }
	while (d >= 1.0)
	  {
	    d *= 0.5;
	    exp += 1;
	  }
      }
    else if (d < 0.5)
      {
	while (d < (1.0 / 65536.0))
	  {
	    d *=  65536.0;
	    exp -= 16;
	  }
	while (d < 0.5)
	  {
	    d *= 2.0;
	    exp -= 1;
	  }
      }

    d *= (4.0 * ((unsigned long int) 1 << (BITS_PER_PART - 2)));
#if BITS_PER_PART == 64
    manl = d;
#endif
#if BITS_PER_PART == 32
    manh = d;
    manl = (d - manh) * (4.0 * ((unsigned long int) 1 << (BITS_PER_PART - 2)));
#endif
  }
#endif /* IEEE */

  sc = (unsigned) (exp + 64 * GMP_NUMB_BITS) % GMP_NUMB_BITS;

  /* We add something here to get rounding right.  */
  exp = (exp + 64 * GMP_NUMB_BITS) / GMP_NUMB_BITS - 64 * GMP_NUMB_BITS / GMP_NUMB_BITS + 1;

#if BITS_PER_PART == 64 && LIMBS_PER_DOUBLE == 2
#if GMP_NAIL_BITS == 0
  if (sc != 0)
    {
      rp[1] = manl >> (GMP_LIMB_BITS - sc);
      rp[0] = manl << sc;
    }
  else
    {
      rp[1] = manl;
      rp[0] = 0;
      exp--;
    }
#else
  if (sc > GMP_NAIL_BITS)
    {
      rp[1] = manl >> (GMP_LIMB_BITS - sc);
      rp[0] = (manl << (sc - GMP_NAIL_BITS)) & GMP_NUMB_MASK;
    }
  else
    {
      if (sc == 0)
	{
	  rp[1] = manl >> GMP_NAIL_BITS;
	  rp[0] = (manl << GMP_NUMB_BITS - GMP_NAIL_BITS) & GMP_NUMB_MASK;
	  exp--;
	}
      else
	{
	  rp[1] = manl >> (GMP_LIMB_BITS - sc);
	  rp[0] = (manl >> (GMP_NAIL_BITS - sc)) & GMP_NUMB_MASK;
	}
    }
#endif
#endif

#if BITS_PER_PART == 64 && LIMBS_PER_DOUBLE == 3
  if (sc > GMP_NAIL_BITS)
    {
      rp[2] = manl >> (GMP_LIMB_BITS - sc);
      rp[1] = (manl << sc - GMP_NAIL_BITS) & GMP_NUMB_MASK;
      if (sc >= 2 * GMP_NAIL_BITS)
	rp[0] = 0;
      else
	rp[0] = (manl << GMP_NUMB_BITS - GMP_NAIL_BITS + sc) & GMP_NUMB_MASK;
    }
  else
    {
      if (sc == 0)
	{
	  rp[2] = manl >> GMP_NAIL_BITS;
	  rp[1] = (manl << GMP_NUMB_BITS - GMP_NAIL_BITS) & GMP_NUMB_MASK;
	  rp[0] = 0;
	  exp--;
	}
      else
	{
	  rp[2] = manl >> (GMP_LIMB_BITS - sc);
	  rp[1] = (manl >> GMP_NAIL_BITS - sc) & GMP_NUMB_MASK;
	  rp[0] = (manl << GMP_NUMB_BITS - GMP_NAIL_BITS + sc) & GMP_NUMB_MASK;
	}
    }
#endif

#if BITS_PER_PART == 32 && LIMBS_PER_DOUBLE == 3
#if GMP_NAIL_BITS == 0
  if (sc != 0)
    {
      rp[2] = manh >> (GMP_LIMB_BITS - sc);
      rp[1] = (manh << sc) | (manl >> (GMP_LIMB_BITS - sc));
      rp[0] = manl << sc;
    }
  else
    {
      rp[2] = manh;
      rp[1] = manl;
      rp[0] = 0;
      exp--;
    }
#else
  if (sc > GMP_NAIL_BITS)
    {
      rp[2] = (manh >> (GMP_LIMB_BITS - sc));
      rp[1] = ((manh << (sc - GMP_NAIL_BITS)) |
	       (manl >> (GMP_LIMB_BITS - sc + GMP_NAIL_BITS))) & GMP_NUMB_MASK;
      if (sc >= 2 * GMP_NAIL_BITS)
	rp[0] = (manl << sc - 2 * GMP_NAIL_BITS) & GMP_NUMB_MASK;
      else
	rp[0] = manl >> (2 * GMP_NAIL_BITS - sc) & GMP_NUMB_MASK;
    }
  else
    {
      if (sc == 0)
	{
	  rp[2] = manh >> GMP_NAIL_BITS;
	  rp[1] = ((manh << GMP_NUMB_BITS - GMP_NAIL_BITS) | (manl >> 2 * GMP_NAIL_BITS)) & GMP_NUMB_MASK;
	  rp[0] = (manl << GMP_NUMB_BITS - 2 * GMP_NAIL_BITS) & GMP_NUMB_MASK;
	  exp--;
	}
      else
	{
	  rp[2] = (manh >> (GMP_LIMB_BITS - sc));
	  rp[1] = (manh >> (GMP_NAIL_BITS - sc)) & GMP_NUMB_MASK;
	  rp[0] = ((manh << (GMP_NUMB_BITS - GMP_NAIL_BITS + sc))
		   | (manl >> (GMP_LIMB_BITS - (GMP_NUMB_BITS - GMP_NAIL_BITS + sc)))) & GMP_NUMB_MASK;
	}
    }
#endif
#endif

#if BITS_PER_PART == 32 && LIMBS_PER_DOUBLE > 3
  if (sc == 0)
    {
      int i;

      for (i = LIMBS_PER_DOUBLE - 1; i >= 0; i--)
	{
	  rp[i] = manh >> (BITS_PER_ULONG - GMP_NUMB_BITS);
	  manh = ((manh << GMP_NUMB_BITS)
		  | (manl >> (BITS_PER_ULONG - GMP_NUMB_BITS)));
	  manl = manl << GMP_NUMB_BITS;
	}
      exp--;
    }
  else
    {
      int i;

      rp[LIMBS_PER_DOUBLE - 1] = (manh >> (GMP_LIMB_BITS - sc));
      manh = (manh << sc) | (manl >> (GMP_LIMB_BITS - sc));
      manl = (manl << sc);
      for (i = LIMBS_PER_DOUBLE - 2; i >= 0; i--)
	{
	  rp[i] = manh >> (BITS_PER_ULONG - GMP_NUMB_BITS);
	  manh = ((manh << GMP_NUMB_BITS)
		  | (manl >> (BITS_PER_ULONG - GMP_NUMB_BITS)));
	  manl = manl << GMP_NUMB_BITS;
	}
  }
#endif

  return exp;
}
