278 lines
5.9 KiB
C
278 lines
5.9 KiB
C
/*---------------------------------------------------------------------------+
|
|
| reg_u_div.c |
|
|
| $Id: reg_u_div.c,v 1.2 2001-10-06 03:53:46 bdenney 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"
|
|
#include <asm/types.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 ( 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);
|
|
|
|
}
|
|
|