225 lines
5.2 KiB
C
225 lines
5.2 KiB
C
/*---------------------------------------------------------------------------+
|
|
| 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);
|
|
}
|