| /*---------------------------------------------------------------------------+ |
| | 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 apm233m@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 *a, FPU_REG *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 *a, FPU_REG *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; |
| } |
| |