/*---------------------------------------------------------------------------+
 |  reg_u_add.c                                                              |
 |  $Id: reg_u_add.c,v 1.7 2003/10/05 12:26:11 sshwarts Exp $
 |                                                                           |
 | Add two valid (TAG_Valid) FPU_REG numbers, of the same sign, 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                             |
 |                                                                           |
 |    Return value is the tag of the answer, or-ed with FPU_Exception if     |
 |    one was raised, or -1 on internal error.                               |
 |                                                                           |
 +---------------------------------------------------------------------------*/

/*
 |    Kernel addition routine FPU_u_add(reg *arg1, reg *arg2, reg *answ).
 |    Takes two valid reg f.p. numbers (TAG_Valid), which are
 |    treated as unsigned numbers,
 |    and returns their sum as a TAG_Valid or TAG_Special f.p. number.
 |    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_add(const FPU_REG *arg1, const FPU_REG *arg2, FPU_REG *answ,
	       u16 control_w, u_char sign, s32 expa, s32 expb)
{
  const FPU_REG *rtmp;
  FPU_REG shifted;
  u32 extent = 0;
  int ediff = expa - expb, ed2, eflag, ovfl, carry;

  if (ediff < 0)
    {
      ediff = -ediff;
      rtmp = arg1;
      arg1 = arg2;
      arg2 = rtmp;
      expa = expb;
    }

  /* Now we have exponent of arg1 >= exponent of arg2 */
  
  answ->exp = expa;

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

  if (ediff == 0)
    {
      extent = 0;
      shifted.sigl = arg2->sigl;
      shifted.sigh = arg2->sigh;
    }
  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)
	{
	  eflag = 0;
	  extent = arg2->sigl;
	  shifted.sigl = arg2->sigh;
	}
      else
        {
          u32 extent2;

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

  answ->sigh = arg1->sigh + shifted.sigh;
  ovfl = shifted.sigh > answ->sigh;
  answ->sigl = arg1->sigl + shifted.sigl;
  if (shifted.sigl > answ->sigl)
    {
      answ->sigh ++;
      if (answ->sigh == 0)
	ovfl = 1;
    }
  if (ovfl)
    {
      carry = extent & 1;
      extent >>= 1;
      extent |= carry;
      if (answ->sigl & 1)
	extent |= 0x80000000;
      answ->sigl >>= 1;
      if (answ->sigh & 1)
	answ->sigl |= 0x80000000;
      answ->sigh >>= 1;
      answ->sigh |= 0x80000000;
      answ->exp ++;
    }

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