58217f5900
WOO HOO!
318 lines
7.5 KiB
C
318 lines
7.5 KiB
C
/*---------------------------------------------------------------------------+
|
|
| reg_add_sub.c |
|
|
| |
|
|
| Functions to add or subtract two registers and put the result in a third. |
|
|
| |
|
|
| Copyright (C) 1992,1993 |
|
|
| W. Metzenthen, 22 Parker St, Ormond, Vic 3163, |
|
|
| Australia. E-mail billm@vaxc.cc.monash.edu.au |
|
|
| |
|
|
| |
|
|
+---------------------------------------------------------------------------*/
|
|
|
|
/*---------------------------------------------------------------------------+
|
|
| For each function, the destination may be any FPU_REG, including one of |
|
|
| the source FPU_REGs. |
|
|
+---------------------------------------------------------------------------*/
|
|
|
|
#include "exception.h"
|
|
#include "reg_constant.h"
|
|
#include "fpu_emu.h"
|
|
#include "control_w.h"
|
|
#include "fpu_system.h"
|
|
|
|
|
|
int reg_add(FPU_REG const *a, FPU_REG const *b, FPU_REG *dest, int control_w)
|
|
{
|
|
char saved_sign = dest->sign;
|
|
int diff;
|
|
|
|
if ( !(a->tag | b->tag) )
|
|
{
|
|
/* Both registers are valid */
|
|
if (!(a->sign ^ b->sign))
|
|
{
|
|
/* signs are the same */
|
|
dest->sign = a->sign;
|
|
if ( reg_u_add(a, b, dest, control_w) )
|
|
{
|
|
dest->sign = saved_sign;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* The signs are different, so do a subtraction */
|
|
diff = a->exp - b->exp;
|
|
if (!diff)
|
|
{
|
|
diff = a->sigh - b->sigh; /* Works only if ms bits are identical */
|
|
if (!diff)
|
|
{
|
|
diff = a->sigl > b->sigl;
|
|
if (!diff)
|
|
diff = -(a->sigl < b->sigl);
|
|
}
|
|
}
|
|
|
|
if (diff > 0)
|
|
{
|
|
dest->sign = a->sign;
|
|
if ( reg_u_sub(a, b, dest, control_w) )
|
|
{
|
|
dest->sign = saved_sign;
|
|
return 1;
|
|
}
|
|
}
|
|
else if ( diff == 0 )
|
|
{
|
|
#ifdef DENORM_OPERAND
|
|
if ( (b->tag == TW_Valid) && (b->exp <= EXP_UNDER) &&
|
|
denormal_operand() )
|
|
return 1;
|
|
#endif DENORM_OPERAND
|
|
reg_move(&CONST_Z, dest);
|
|
/* sign depends upon rounding mode */
|
|
dest->sign = ((control_w & CW_RC) != RC_DOWN)
|
|
? SIGN_POS : SIGN_NEG;
|
|
}
|
|
else
|
|
{
|
|
dest->sign = b->sign;
|
|
if ( reg_u_sub(b, a, dest, control_w) )
|
|
{
|
|
dest->sign = saved_sign;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
if ( (a->tag == TW_NaN) || (b->tag == TW_NaN) )
|
|
{ return real_2op_NaN(a, b, dest); }
|
|
else if (a->tag == TW_Zero)
|
|
{
|
|
if (b->tag == TW_Zero)
|
|
{
|
|
char different_signs = a->sign ^ b->sign;
|
|
/* Both are zero, result will be zero. */
|
|
reg_move(a, dest);
|
|
if (different_signs)
|
|
{
|
|
/* Signs are different. */
|
|
/* Sign of answer depends upon rounding mode. */
|
|
dest->sign = ((control_w & CW_RC) != RC_DOWN)
|
|
? SIGN_POS : SIGN_NEG;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef DENORM_OPERAND
|
|
if ( (b->tag == TW_Valid) && (b->exp <= EXP_UNDER) &&
|
|
denormal_operand() )
|
|
return 1;
|
|
#endif DENORM_OPERAND
|
|
reg_move(b, dest);
|
|
}
|
|
return 0;
|
|
}
|
|
else if (b->tag == TW_Zero)
|
|
{
|
|
#ifdef DENORM_OPERAND
|
|
if ( (a->tag == TW_Valid) && (a->exp <= EXP_UNDER) &&
|
|
denormal_operand() )
|
|
return 1;
|
|
#endif DENORM_OPERAND
|
|
reg_move(a, dest); return 0;
|
|
}
|
|
else if (a->tag == TW_Infinity)
|
|
{
|
|
if (b->tag != TW_Infinity)
|
|
{
|
|
#ifdef DENORM_OPERAND
|
|
if ( (b->tag == TW_Valid) && (b->exp <= EXP_UNDER) &&
|
|
denormal_operand() )
|
|
return 1;
|
|
#endif DENORM_OPERAND
|
|
reg_move(a, dest); return 0;
|
|
}
|
|
if (a->sign == b->sign)
|
|
{
|
|
/* They are both + or - infinity */
|
|
reg_move(a, dest); return 0;
|
|
}
|
|
return arith_invalid(dest); /* Infinity-Infinity is undefined. */
|
|
}
|
|
else if (b->tag == TW_Infinity)
|
|
{
|
|
#ifdef DENORM_OPERAND
|
|
if ( (a->tag == TW_Valid) && (a->exp <= EXP_UNDER) &&
|
|
denormal_operand() )
|
|
return 1;
|
|
#endif DENORM_OPERAND
|
|
reg_move(b, dest); return 0;
|
|
}
|
|
}
|
|
#ifdef PARANOID
|
|
EXCEPTION(EX_INTERNAL|0x101);
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* Subtract b from a. (a-b) -> dest */
|
|
int reg_sub(FPU_REG const *a, FPU_REG const *b, FPU_REG *dest, int control_w)
|
|
{
|
|
char saved_sign = dest->sign;
|
|
int diff;
|
|
|
|
if ( !(a->tag | b->tag) )
|
|
{
|
|
/* Both registers are valid */
|
|
diff = a->exp - b->exp;
|
|
if (!diff)
|
|
{
|
|
diff = a->sigh - b->sigh; /* Works only if ms bits are identical */
|
|
if (!diff)
|
|
{
|
|
diff = a->sigl > b->sigl;
|
|
if (!diff)
|
|
diff = -(a->sigl < b->sigl);
|
|
}
|
|
}
|
|
|
|
switch (a->sign*2 + b->sign)
|
|
{
|
|
case 0: /* P - P */
|
|
case 3: /* N - N */
|
|
if (diff > 0)
|
|
{
|
|
/* |a| > |b| */
|
|
dest->sign = a->sign;
|
|
if ( reg_u_sub(a, b, dest, control_w) )
|
|
{
|
|
dest->sign = saved_sign;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
else if ( diff == 0 )
|
|
{
|
|
#ifdef DENORM_OPERAND
|
|
if ( (b->tag == TW_Valid) && (b->exp <= EXP_UNDER) &&
|
|
denormal_operand() )
|
|
return 1;
|
|
#endif DENORM_OPERAND
|
|
reg_move(&CONST_Z, dest);
|
|
/* sign depends upon rounding mode */
|
|
dest->sign = ((control_w & CW_RC) != RC_DOWN)
|
|
? SIGN_POS : SIGN_NEG;
|
|
}
|
|
else
|
|
{
|
|
dest->sign = a->sign ^ SIGN_POS^SIGN_NEG;
|
|
if ( reg_u_sub(b, a, dest, control_w) )
|
|
{
|
|
dest->sign = saved_sign;
|
|
return 1;
|
|
}
|
|
}
|
|
break;
|
|
case 1: /* P - N */
|
|
dest->sign = SIGN_POS;
|
|
if ( reg_u_add(a, b, dest, control_w) )
|
|
{
|
|
dest->sign = saved_sign;
|
|
return 1;
|
|
}
|
|
break;
|
|
case 2: /* N - P */
|
|
dest->sign = SIGN_NEG;
|
|
if ( reg_u_add(a, b, dest, control_w) )
|
|
{
|
|
dest->sign = saved_sign;
|
|
return 1;
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
if ( (a->tag == TW_NaN) || (b->tag == TW_NaN) )
|
|
{ return real_2op_NaN(b, a, dest); }
|
|
else if (b->tag == TW_Zero)
|
|
{
|
|
if (a->tag == TW_Zero)
|
|
{
|
|
char same_signs = !(a->sign ^ b->sign);
|
|
/* Both are zero, result will be zero. */
|
|
reg_move(a, dest); /* Answer for different signs. */
|
|
if (same_signs)
|
|
{
|
|
/* Sign depends upon rounding mode */
|
|
dest->sign = ((control_w & CW_RC) != RC_DOWN)
|
|
? SIGN_POS : SIGN_NEG;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef DENORM_OPERAND
|
|
if ( (a->tag == TW_Valid) && (a->exp <= EXP_UNDER) &&
|
|
denormal_operand() )
|
|
return 1;
|
|
#endif DENORM_OPERAND
|
|
reg_move(a, dest);
|
|
}
|
|
return 0;
|
|
}
|
|
else if (a->tag == TW_Zero)
|
|
{
|
|
#ifdef DENORM_OPERAND
|
|
if ( (b->tag == TW_Valid) && (b->exp <= EXP_UNDER) &&
|
|
denormal_operand() )
|
|
return 1;
|
|
#endif DENORM_OPERAND
|
|
reg_move(b, dest);
|
|
dest->sign ^= SIGN_POS^SIGN_NEG;
|
|
return 0;
|
|
}
|
|
else if (a->tag == TW_Infinity)
|
|
{
|
|
if (b->tag != TW_Infinity)
|
|
{
|
|
#ifdef DENORM_OPERAND
|
|
if ( (b->tag == TW_Valid) && (b->exp <= EXP_UNDER) &&
|
|
denormal_operand() )
|
|
return 1;
|
|
#endif DENORM_OPERAND
|
|
reg_move(a, dest); return 0;
|
|
}
|
|
/* Both args are Infinity */
|
|
if (a->sign == b->sign)
|
|
{
|
|
/* Infinity-Infinity is undefined. */
|
|
return arith_invalid(dest);
|
|
}
|
|
reg_move(a, dest);
|
|
return 0;
|
|
}
|
|
else if (b->tag == TW_Infinity)
|
|
{
|
|
#ifdef DENORM_OPERAND
|
|
if ( (a->tag == TW_Valid) && (a->exp <= EXP_UNDER) &&
|
|
denormal_operand() )
|
|
return 1;
|
|
#endif DENORM_OPERAND
|
|
reg_move(b, dest);
|
|
dest->sign ^= SIGN_POS^SIGN_NEG;
|
|
return 0;
|
|
}
|
|
}
|
|
#ifdef PARANOID
|
|
EXCEPTION(EX_INTERNAL|0x110);
|
|
#endif
|
|
return 1;
|
|
}
|
|
|