Bochs/bochs/fpu/reg_u_sub.c

223 lines
5.2 KiB
C

/*---------------------------------------------------------------------------+
| reg_u_sub.c |
| $Id: reg_u_sub.c,v 1.2 2001-10-06 03:53:46 bdenney 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 )
{
EXCEPTION(EX_INTERNAL|0x206);
return -1;
}
#endif
answ.exp = expa;
#ifdef PARANOID
if ( !(arg1->sigh & 0x80000000) || !(arg2->sigh & 0x80000000) )
{
EXCEPTION(EX_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 */
EXCEPTION(EX_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, 0, 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 */
EXCEPTION(EX_INTERNAL|0x210);
return -1;
}
dest->sigh = extent;
dest->sigl = extent = 0;
dest->exp -= 64;
return FPU_round(dest, extent, 0, 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, 0, control_w, sign);
}