/*---------------------------------------------------------------------------+
 |  reg_u_div.c                                                              |
 |  $Id: reg_u_div.c,v 1.4 2003/04/20 19:20:08 sshwarts Exp $
 |                                                                           |
 | Divide one FPU_REG by another and put the result in a destination FPU_REG.|
 |                                                                           |
 | Copyright (C) 1992,1993,1995,1997,1999                                    |
 |                  W. Metzenthen, 22 Parker St, Ormond, Vic 3163, Australia |
 |                  E-mail   billm@melbpc.org.au                             |
 |                                                                           |
 |                                                                           |
 +---------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------+
 |                                                                           |
 |  Does not compute the destination exponent, but does adjust it.           |
 |                                                                           |
 |    Return value is the tag of the answer, or-ed with FPU_Exception if     |
 |    one was raised, or -1 on internal error.                               |
 +---------------------------------------------------------------------------*/

#include "exception.h"
#include "fpu_emu.h"
#include "control_w.h"


int FPU_u_div(const FPU_REG *a, const FPU_REG *b, FPU_REG *dest,
	      u16 control_w, u8 sign)
{
  s32 exp;
  u32 divr32, rem, rat1, rat2, work32, accum3, prodh;
  u64 work64, divr64, prod64, accum64;
  u8 ovfl;

  exp = (s32)a->exp - (s32)b->exp;

  /* if the above subtraction of signed 16-bit values overflowed, substitute
         the maximum positive exponent to force FPU_round to produce overflow */
  if (exp > 0x7FFF) 
    exp = 0x7FFF;

  if (exp < EXP_WAY_UNDER)
    exp = EXP_WAY_UNDER;

  dest->exp = exp;
#ifdef PARANOID
  if (!(b->sigh & 0x80000000))
    {
      EXCEPTION(EX_INTERNAL|0x202);
    }
#endif

  work64 = significand(a);

  /* We can save a lot of time if the divisor has all its lowest
     32 bits equal to zero. */
  if (b->sigl == 0)
    {
      divr32 = b->sigh;
      ovfl = a->sigh >= divr32;
      rat1 = work64 / divr32;
      rem = work64 % divr32;
      work64 = rem;
      work64 <<= 32;
      rat2 = work64 / divr32;
      rem = work64 % divr32;

      work64 = rem;
      work64 <<= 32;
      rem = work64 / divr32;

      if (ovfl)
	{
	  rem >>= 1;
	  if (rat2 & 1)
	    rem |= 0x80000000;
	  rat2 >>= 1;
	  if (rat1 & 1)
	    rat2 |= 0x80000000;
	  rat1 >>= 1;
	  rat1 |= 0x80000000;
	  dest->exp ++;
	}
      dest->sigh = rat1;
      dest->sigl = rat2;

      dest->exp --;
      return FPU_round(dest, rem, 0, control_w, sign);
    }

  /* This may take a little time... */

  accum64 = work64;
  divr64 = significand(b);

  if ((ovfl = accum64 >= divr64))
    accum64 -= divr64;
  divr32 = b->sigh+1;

  if (divr32 != 0)
    {
      rat1 = accum64 / divr32;
    }
  else
    rat1 = accum64 >> 32;
  prod64 = rat1 * (u64)b->sigh;

  accum64 -= prod64;
  prod64 = rat1 * (u64)b->sigl;
  accum3 = prod64;
  if (accum3)
    {
      accum3 = -accum3;
      accum64 --;
    }
  prodh = prod64 >> 32;
  accum64 -= prodh;

  work32 = accum64 >> 32;
  if (work32)
    {
#ifdef PARANOID
      if (work32 != 1)
	{
	  EXCEPTION(EX_INTERNAL|0x203);
	}
#endif

      /* Need to subtract the divisor once more. */
      work32 = accum3;
      accum3 = work32 - b->sigl;
      if (accum3 > work32)
	accum64 --;
      rat1 ++;
      accum64 -= b->sigh;

#ifdef PARANOID
      if ((accum64 >> 32))
	{
	  EXCEPTION(EX_INTERNAL|0x203);
	}
#endif
    }

  /* Now we essentially repeat what we have just done, but shifted
     32 bits. */

  accum64 <<= 32;
  accum64 |= accum3;
  if (accum64 >= divr64)
    {
      accum64 -= divr64;
      rat1 ++;
    }
  if (divr32 != 0)
    {
      rat2 = accum64 / divr32;
    }
  else
    rat2 = accum64 >> 32;
  prod64 = rat2 * (u64)b->sigh;

  accum64 -= prod64;
  prod64 = rat2 * (u64)b->sigl;
  accum3 = prod64;
  if (accum3)
    {
      accum3 = -accum3;
      accum64 --;
    }
  prodh = prod64 >> 32;
  accum64 -= prodh;

  work32 = accum64 >> 32;
  if (work32)
    {
#ifdef PARANOID
      if (work32 != 1)
	{
	  EXCEPTION(EX_INTERNAL|0x203);
	}
#endif

      /* Need to subtract the divisor once more. */
      work32 = accum3;
      accum3 = work32 - b->sigl;
      if (accum3 > work32)
	accum64 --;
      rat2 ++;
      if (rat2 == 0)
	rat1 ++;
      accum64 -= b->sigh;

#ifdef PARANOID
      if ((accum64 >> 32))
	{
	  EXCEPTION(EX_INTERNAL|0x203);
	}
#endif
    }

  /* Tidy up the remainder */

  accum64 <<= 32;
  accum64 |= accum3;
  if (accum64 >= divr64)
    {
      accum64 -= divr64;
      rat2 ++;
      if (rat2 == 0)
	{
	  rat1 ++;
#ifdef PARANOID
	  /* No overflow should be possible here */
	  if (rat1 == 0)
	    {
	      EXCEPTION(EX_INTERNAL|0x203);
	    }
	}
#endif
    }

  /* The basic division is done, now we must be careful with the
     remainder. */

  if (ovfl)
    {
      if (rat2 & 1)
	rem = 0x80000000;
      else
	rem = 0;
      rat2 >>= 1;
      if (rat1 & 1)
	rat2 |= 0x80000000;
      rat1 >>= 1;
      rat1 |= 0x80000000;

      if (accum64)
	rem |= 0xff0000;

      dest->exp ++;
    }
  else
    {
      /* Now we just need to know how large the remainder is
	 relative to half the divisor. */
      if (accum64 == 0)
	rem = 0;
      else
	{
	  accum3 = accum64 >> 32;
	  if (accum3 & 0x80000000)
	    {
	      /* The remainder is definitely larger than 1/2 divisor. */
	      rem = 0xff000000;
	    }
	  else
	    {
	      accum64 <<= 1;
	      if (accum64 >= divr64)
		{
		  accum64 -= divr64;
		  if (accum64 == 0)
		    rem = 0x80000000;
		  else
		    rem = 0xff000000;
		}
	      else
		rem = 0x7f000000;
	    }
	}
    }

  dest->sigh = rat1;
  dest->sigl = rat2;

  dest->exp --;
  return FPU_round(dest, rem, 0, control_w, sign);
}

