/*---------------------------------------------------------------------------+
 |  reg_u_sub.c                                                              |
 |  $Id: reg_u_sub.c,v 1.7 2003/10/05 12:26:11 sshwarts Exp $
 |                                                                           |
 | Core floating point subtraction routine.                                  |
 |                                                                           |
 | Copyright (C) 1992,1993,1995,1997,1999                                    |
 |                  W. Metzenthen, 22 Parker St, Ormond, Vic 3163, Australia |
 |                  E-mail   billm@melbpc.org.au                             |
 |                                                                           |
 |    Return value is the tag of the answer, or-ed with FPU_Exception if     |
 |    one was raised, or -1 on internal error.                               |
 |                                                                           |
 +---------------------------------------------------------------------------*/

/*
 |    Kernel subtraction routine FPU_u_sub(reg *arg1, reg *arg2, reg *answ).
 |    Takes two valid reg f.p. numbers (TAG_Valid), which are
 |    treated as unsigned numbers,
 |    and returns their difference as a TAG_Valid or TAG_Zero f.p.
 |    number.
 |    The first number (arg1) must be the larger.
 |    The returned number is normalized.
 |    Basic checks are performed if PARANOID is defined.
 */

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


int  FPU_u_sub(const FPU_REG *arg1, const FPU_REG *arg2, FPU_REG *dest,
	       u16 control_w, u_char sign, int expa, int expb)
{
  FPU_REG shifted, answ;
  u32 extent;
  int ediff = expa - expb, ed2, borrow;

#ifdef PARANOID
  if (ediff < 0)
    {
      INTERNAL(0x206);
      return -1;
    }
#endif
  
  answ.exp = expa;

#ifdef PARANOID
  if (!(arg1->sigh & 0x80000000) || !(arg2->sigh & 0x80000000))
    {
      INTERNAL(0x209);
      return -1;
    }
#endif

  if (ediff == 0)
    {
      shifted.sigl = arg2->sigl;
      shifted.sigh = arg2->sigh;
      extent = 0;
    }
  else if (ediff < 32)
    {
      ed2 = 32 - ediff;
      extent = arg2->sigl << ed2;
      shifted.sigl = arg2->sigl >> ediff;
      shifted.sigl |= (arg2->sigh << ed2);
      shifted.sigh = arg2->sigh >> ediff;
    }
  else if (ediff < 64)
    {
      ediff -= 32;
      if (! ediff)
	{
	  extent = arg2->sigl;
	  shifted.sigl = arg2->sigh;
	  shifted.sigh = 0;
	}
      else
	{
	  ed2 = 32 - ediff;
	  extent = arg2->sigl >> ediff;
	  extent |= (arg2->sigh << ed2);
	  if (arg2->sigl << ed2)
	    extent |= 1;
	  shifted.sigl = arg2->sigh >> ediff;
	  shifted.sigh = 0;
	}
    }
  else
    {
      ediff -= 64;
      if (! ediff)
	{
	  extent = arg2->sigh;
	  if (arg2->sigl)
	    extent |= 1;
	  shifted.sigl = 0;
	  shifted.sigh = 0;
	}
      else
	{
	  if (ediff < 32)
	    {
	      extent = arg2->sigh >> ediff;
	      if (arg2->sigl || (arg2->sigh << (32-ediff)))
		extent |= 1;
	    }
	  else
	    extent = 1;
	  shifted.sigl = 0;
	  shifted.sigh = 0;
	}
    }

  extent = -extent;
  borrow = extent;
  answ.sigl = arg1->sigl - shifted.sigl;
  if (answ.sigl > arg1->sigl)
    {
      if (borrow)
	answ.sigl --;
      borrow = 1;
    }
  else if (borrow)
    {
      answ.sigl --;
      if (answ.sigl != 0xffffffff)
	borrow = 0;
    }
  answ.sigh = arg1->sigh - shifted.sigh;
  if (answ.sigh > arg1->sigh)
    {
      if (borrow)
	answ.sigh --;
      borrow = 1;
    }
  else if (borrow)
    {
      answ.sigh --;
      if (answ.sigh != 0xffffffff)
	borrow = 0;
    }

#ifdef PARANOID
  if (borrow)
    {
      /* This can only occur if the code is bugged */
      INTERNAL(0x212);
      return -1;
    }
#endif

  if (answ.sigh & 0x80000000)
    {
      /*
	The simpler "*dest = answ" is broken in gcc
      */
      dest->exp = answ.exp;
      dest->sigh = answ.sigh;
      dest->sigl = answ.sigl;
      return FPU_round(dest, extent, control_w, sign);
    }

  if (answ.sigh == 0)
    {
      if (answ.sigl)
	{
	  answ.sigh = answ.sigl;
	  answ.sigl = extent;
	  extent = 0;
	  answ.exp -= 32;
	}
      else if (extent)
	{
/*
 *	 A rare case, the only one which is non-zero if we got here
 *         is:           1000000 .... 0000
 *                      -0111111 .... 1111 1
 *                       -------------------- 
 *                       0000000 .... 0000 1 
 */
	  if (extent != 0x80000000)
	    {
	      /* This can only occur if the code is bugged */
	      INTERNAL(0x210);
	      return -1;
	    }
	  dest->sigh = extent;
	  dest->sigl = extent = 0;
     dest->exp &= 0x7FFF;
     dest->exp -= 64;
     if (arg1->sigh == 0x80000000 && arg2->sigh == 0x80000000)
         dest->exp++;
     dest->exp -= EXTENDED_Ebias;
     return FPU_round(dest, extent, control_w, sign);
	}
      else
	{
	  dest->exp = 0;
	  dest->sigh = dest->sigl = 0;
	  return TAG_Zero;
	}
    }

  while (!(answ.sigh & 0x80000000))
    {
      answ.sigh <<= 1;
      if (answ.sigl & 0x80000000)
	answ.sigh |= 1;
      answ.sigl <<= 1;
      if (extent & 0x80000000)
	answ.sigl |= 1;
      extent <<= 1;
      answ.exp --;
    }

  dest->exp = answ.exp;
  dest->sigh = answ.sigh;
  dest->sigl = answ.sigl;

  return FPU_round(dest, extent, control_w, sign);
}
