540 lines
13 KiB
C
540 lines
13 KiB
C
/*---------------------------------------------------------------------------+
|
|
| reg_round.c |
|
|
| $Id: reg_round.c,v 1.2 2001-10-06 03:53:46 bdenney Exp $
|
|
| |
|
|
| Rounding/truncation/etc for FPU basic arithmetic functions. |
|
|
| |
|
|
| Copyright (C) 1993,1995,1997,1999 |
|
|
| W. Metzenthen, 22 Parker St, Ormond, Vic 3163, |
|
|
| Australia. E-mail billm@melbpc.org.au |
|
|
| |
|
|
| This code has four possible entry points. |
|
|
| The following must be entered by a jmp instruction: |
|
|
| fpu_reg_round, fpu_reg_round_sqrt, and fpu_Arith_exit. |
|
|
| |
|
|
| The FPU_round entry point is intended to be used by C code. |
|
|
| |
|
|
| Return value is the tag of the answer, or-ed with FPU_Exception if |
|
|
| one was raised, or -1 on internal error. |
|
|
| |
|
|
| For correct "up" and "down" rounding, the argument must have the correct |
|
|
| sign. |
|
|
| |
|
|
+---------------------------------------------------------------------------*/
|
|
|
|
/*---------------------------------------------------------------------------+
|
|
| |
|
|
| The significand and its extension are assumed to be exact in the |
|
|
| following sense: |
|
|
| If the significand by itself is the exact result then the significand |
|
|
| extension (%edx) must contain 0, otherwise the significand extension |
|
|
| must be non-zero. |
|
|
| If the significand extension is non-zero then the significand is |
|
|
| smaller than the magnitude of the correct exact result by an amount |
|
|
| greater than zero and less than one ls bit of the significand. |
|
|
| The significand extension is only required to have three possible |
|
|
| non-zero values: |
|
|
| less than 0x80000000 <=> the significand is less than 1/2 an ls |
|
|
| bit smaller than the magnitude of the |
|
|
| true exact result. |
|
|
| exactly 0x80000000 <=> the significand is exactly 1/2 an ls bit |
|
|
| smaller than the magnitude of the true |
|
|
| exact result. |
|
|
| greater than 0x80000000 <=> the significand is more than 1/2 an ls |
|
|
| bit smaller than the magnitude of the |
|
|
| true exact result. |
|
|
| |
|
|
+---------------------------------------------------------------------------*/
|
|
|
|
/*---------------------------------------------------------------------------+
|
|
| The code in this module has become quite complex, but it should handle |
|
|
| all of the FPU flags which are set at this stage of the basic arithmetic |
|
|
| computations. |
|
|
| There are a few rare cases where the results are not set identically to |
|
|
| a real FPU. These require a bit more thought because at this stage the |
|
|
| results of the code here appear to be more consistent... |
|
|
| This may be changed in a future version. |
|
|
+---------------------------------------------------------------------------*/
|
|
|
|
|
|
#include "fpu_emu.h"
|
|
#include "exception.h"
|
|
#include "control_w.h"
|
|
|
|
/* Flags for FPU_bits_lost */
|
|
#define LOST_DOWN 1
|
|
#define LOST_UP 2
|
|
|
|
/* Flags for FPU_denormal */
|
|
#define DENORMAL 1
|
|
#define UNMASKED_UNDERFLOW 2
|
|
|
|
|
|
int round_up_64(FPU_REG *x, u32 extent)
|
|
{
|
|
x->sigl ++;
|
|
if ( x->sigl == 0 )
|
|
{
|
|
x->sigh ++;
|
|
if ( x->sigh == 0 )
|
|
{
|
|
x->sigh = 0x80000000;
|
|
x->exp ++;
|
|
}
|
|
}
|
|
return LOST_UP;
|
|
}
|
|
|
|
|
|
int truncate_64(FPU_REG *x, u32 extent)
|
|
{
|
|
return LOST_DOWN;
|
|
}
|
|
|
|
|
|
int round_up_53(FPU_REG *x, u32 extent)
|
|
{
|
|
x->sigl &= 0xfffff800;
|
|
x->sigl += 0x800;
|
|
if ( x->sigl == 0 )
|
|
{
|
|
x->sigh ++;
|
|
if ( x->sigh == 0 )
|
|
{
|
|
x->sigh = 0x80000000;
|
|
x->exp ++;
|
|
}
|
|
}
|
|
return LOST_UP;
|
|
}
|
|
|
|
|
|
int truncate_53(FPU_REG *x, u32 extent)
|
|
{
|
|
x->sigl &= 0xfffff800;
|
|
return LOST_DOWN;
|
|
}
|
|
|
|
|
|
int round_up_24(FPU_REG *x, u32 extent)
|
|
{
|
|
x->sigl = 0;
|
|
x->sigh &= 0xffffff00;
|
|
x->sigh += 0x100;
|
|
if ( x->sigh == 0 )
|
|
{
|
|
x->sigh = 0x80000000;
|
|
x->exp ++;
|
|
}
|
|
return LOST_UP;
|
|
}
|
|
|
|
|
|
int truncate_24(FPU_REG *x, u32 extent)
|
|
{
|
|
x->sigl = 0;
|
|
x->sigh &= 0xffffff00;
|
|
return LOST_DOWN;
|
|
}
|
|
|
|
|
|
int FPU_round(FPU_REG *x, u32 extent, int dummy, u16 control_w, u8 sign)
|
|
{
|
|
u64 work;
|
|
u32 leading;
|
|
s16 expon = x->exp;
|
|
int FPU_bits_lost = 0, FPU_denormal, shift, tag;
|
|
|
|
if ( expon <= EXP_UNDER )
|
|
{
|
|
/* A denormal or zero */
|
|
if ( control_w & CW_Underflow )
|
|
{
|
|
/* Underflow is masked. */
|
|
FPU_denormal = DENORMAL;
|
|
shift = EXP_UNDER+1 - expon;
|
|
if ( shift >= 64 )
|
|
{
|
|
if ( shift == 64 )
|
|
{
|
|
x->exp += 64;
|
|
if ( extent | x->sigl )
|
|
extent = x->sigh | 1;
|
|
else
|
|
extent = x->sigh;
|
|
}
|
|
else
|
|
{
|
|
x->exp = EXP_UNDER+1;
|
|
extent = 1;
|
|
}
|
|
significand(x) = 0;
|
|
}
|
|
else
|
|
{
|
|
x->exp += shift;
|
|
if ( shift >= 32 )
|
|
{
|
|
shift -= 32;
|
|
if ( shift )
|
|
{
|
|
extent |= x->sigl;
|
|
work = significand(x) >> shift;
|
|
if ( extent )
|
|
extent = work | 1;
|
|
else
|
|
extent = work;
|
|
x->sigl = x->sigh >>= shift;
|
|
}
|
|
else
|
|
{
|
|
if ( extent )
|
|
extent = x->sigl | 1;
|
|
else
|
|
extent = x->sigl;
|
|
x->sigl = x->sigh;
|
|
}
|
|
x->sigh = 0;
|
|
}
|
|
else
|
|
{
|
|
/* Shift by 1 to 32 places. */
|
|
work = x->sigl;
|
|
work <<= 32;
|
|
work |= extent;
|
|
work >>= shift;
|
|
if ( extent )
|
|
extent = 1;
|
|
extent |= work;
|
|
significand(x) >>= shift;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Unmasked underflow. */
|
|
FPU_denormal = UNMASKED_UNDERFLOW;
|
|
}
|
|
}
|
|
else
|
|
FPU_denormal = 0;
|
|
|
|
switch ( control_w & CW_PC )
|
|
{
|
|
case 01:
|
|
#ifndef PECULIAR_486
|
|
/* With the precision control bits set to 01 "(reserved)", a real 80486
|
|
behaves as if the precision control bits were set to 11 "64 bits" */
|
|
#ifdef PARANOID
|
|
EXCEPTION(EX_INTERNAL|0x236);
|
|
return -1;
|
|
#endif
|
|
#endif
|
|
/* Fall through to the 64 bit case. */
|
|
case PR_64_BITS:
|
|
if ( extent )
|
|
{
|
|
switch ( control_w & CW_RC )
|
|
{
|
|
case RC_RND: /* Nearest or even */
|
|
/* See if there is exactly half a ulp. */
|
|
if ( extent == 0x80000000 )
|
|
{
|
|
/* Round to even. */
|
|
if ( x->sigl & 0x1 )
|
|
/* Odd */
|
|
FPU_bits_lost = round_up_64(x, extent);
|
|
else
|
|
/* Even */
|
|
FPU_bits_lost = truncate_64(x, extent);
|
|
}
|
|
else if ( extent > 0x80000000 )
|
|
{
|
|
/* Greater than half */
|
|
FPU_bits_lost = round_up_64(x, extent);
|
|
}
|
|
else
|
|
{
|
|
/* Less than half */
|
|
FPU_bits_lost = truncate_64(x, extent);
|
|
}
|
|
break;
|
|
|
|
case RC_CHOP: /* Truncate */
|
|
FPU_bits_lost = truncate_64(x, extent);
|
|
break;
|
|
|
|
case RC_UP: /* Towards +infinity */
|
|
if ( sign == SIGN_POS)
|
|
{
|
|
FPU_bits_lost = round_up_64(x, extent);
|
|
}
|
|
else
|
|
{
|
|
FPU_bits_lost = truncate_64(x, extent);
|
|
}
|
|
break;
|
|
|
|
case RC_DOWN: /* Towards -infinity */
|
|
if ( sign != SIGN_POS)
|
|
{
|
|
FPU_bits_lost = round_up_64(x, extent);
|
|
}
|
|
else
|
|
{
|
|
FPU_bits_lost = truncate_64(x, extent);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
EXCEPTION(EX_INTERNAL|0x231);
|
|
return -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PR_53_BITS:
|
|
leading = x->sigl & 0x7ff;
|
|
if ( extent || leading )
|
|
{
|
|
switch ( control_w & CW_RC )
|
|
{
|
|
case RC_RND: /* Nearest or even */
|
|
/* See if there is exactly half a ulp. */
|
|
if ( leading == 0x400 )
|
|
{
|
|
if ( extent == 0 )
|
|
{
|
|
/* Round to even. */
|
|
if ( x->sigl & 0x800 )
|
|
/* Odd */
|
|
FPU_bits_lost = round_up_53(x, extent);
|
|
else
|
|
/* Even */
|
|
FPU_bits_lost = truncate_53(x, extent);
|
|
}
|
|
else
|
|
{
|
|
/* Greater than half */
|
|
FPU_bits_lost = round_up_53(x, extent);
|
|
}
|
|
}
|
|
else if ( leading > 0x400 )
|
|
{
|
|
/* Greater than half */
|
|
FPU_bits_lost = round_up_53(x, extent);
|
|
}
|
|
else
|
|
{
|
|
/* Less than half */
|
|
FPU_bits_lost = truncate_53(x, extent);
|
|
}
|
|
break;
|
|
|
|
case RC_CHOP: /* Truncate */
|
|
FPU_bits_lost = truncate_53(x, extent);
|
|
break;
|
|
|
|
case RC_UP: /* Towards +infinity */
|
|
if ( sign == SIGN_POS)
|
|
{
|
|
FPU_bits_lost = round_up_53(x, extent);
|
|
}
|
|
else
|
|
{
|
|
FPU_bits_lost = truncate_53(x, extent);
|
|
}
|
|
break;
|
|
|
|
case RC_DOWN: /* Towards -infinity */
|
|
if ( sign != SIGN_POS)
|
|
{
|
|
FPU_bits_lost = round_up_53(x, extent);
|
|
}
|
|
else
|
|
{
|
|
FPU_bits_lost = truncate_53(x, extent);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
EXCEPTION(EX_INTERNAL|0x231);
|
|
return -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PR_24_BITS:
|
|
leading = x->sigh & 0xff;
|
|
if ( leading || x->sigl || extent )
|
|
{
|
|
switch ( control_w & CW_RC )
|
|
{
|
|
case RC_RND: /* Nearest or even */
|
|
/* See if there is exactly half a ulp. */
|
|
if ( leading == 0x80 )
|
|
{
|
|
if ( (x->sigl == 0) && (extent == 0) )
|
|
{
|
|
/* Round to even. */
|
|
if ( x->sigh & 0x100 )
|
|
/* Odd */
|
|
FPU_bits_lost = round_up_24(x, extent);
|
|
else
|
|
/* Even */
|
|
FPU_bits_lost = truncate_24(x, extent);
|
|
}
|
|
else
|
|
{
|
|
/* Greater than half */
|
|
FPU_bits_lost = round_up_24(x, extent);
|
|
}
|
|
}
|
|
else if ( leading > 0x80 )
|
|
{
|
|
/* Greater than half */
|
|
FPU_bits_lost = round_up_24(x, extent);
|
|
}
|
|
else
|
|
{
|
|
/* Less than half */
|
|
FPU_bits_lost = truncate_24(x, extent);
|
|
}
|
|
break;
|
|
|
|
case RC_CHOP: /* Truncate */
|
|
FPU_bits_lost = truncate_24(x, extent);
|
|
break;
|
|
|
|
case RC_UP: /* Towards +infinity */
|
|
if ( sign == SIGN_POS)
|
|
{
|
|
FPU_bits_lost = round_up_24(x, extent);
|
|
}
|
|
else
|
|
{
|
|
FPU_bits_lost = truncate_24(x, extent);
|
|
}
|
|
break;
|
|
|
|
case RC_DOWN: /* Towards -infinity */
|
|
if ( sign != SIGN_POS)
|
|
{
|
|
FPU_bits_lost = round_up_24(x, extent);
|
|
}
|
|
else
|
|
{
|
|
FPU_bits_lost = truncate_24(x, extent);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
EXCEPTION(EX_INTERNAL|0x231);
|
|
return -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
#ifdef PARANOID
|
|
EXCEPTION(EX_INTERNAL|0x230);
|
|
return -1;
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
tag = TAG_Valid;
|
|
|
|
if ( FPU_denormal )
|
|
{
|
|
/* Undo the de-normalisation. */
|
|
if ( FPU_denormal == UNMASKED_UNDERFLOW )
|
|
{
|
|
if ( x->exp <= EXP_UNDER )
|
|
{
|
|
/* Increase the exponent by the magic number */
|
|
x->exp += 3 * (1 << 13);
|
|
EXCEPTION(EX_Underflow);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( x->exp != EXP_UNDER+1 )
|
|
{
|
|
EXCEPTION(EX_INTERNAL|0x234);
|
|
}
|
|
if ( (x->sigh == 0) && (x->sigl == 0) )
|
|
{
|
|
/* Underflow to zero */
|
|
set_precision_flag_down();
|
|
EXCEPTION(EX_Underflow);
|
|
x->exp = EXP_UNDER;
|
|
tag = TAG_Zero;
|
|
FPU_bits_lost = 0; /* Stop another call to
|
|
set_precision_flag_down() */
|
|
}
|
|
else
|
|
{
|
|
if ( x->sigh & 0x80000000 )
|
|
{
|
|
#ifdef PECULIAR_486
|
|
/*
|
|
* This implements a special feature of 80486 behaviour.
|
|
* Underflow will be signalled even if the number is
|
|
* not a denormal after rounding.
|
|
* This difference occurs only for masked underflow, and not
|
|
* in the unmasked case.
|
|
* Actual 80486 behaviour differs from this in some circumstances.
|
|
*/
|
|
/* Will be masked underflow */
|
|
#else
|
|
/* No longer a denormal */
|
|
#endif
|
|
}
|
|
else
|
|
#ifndef PECULIAR_486
|
|
{
|
|
#endif
|
|
x->exp --;
|
|
|
|
if ( FPU_bits_lost )
|
|
{
|
|
/* There must be a masked underflow */
|
|
EXCEPTION(EX_Underflow);
|
|
}
|
|
|
|
tag = TAG_Special;
|
|
#ifndef PECULIAR_486
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if ( FPU_bits_lost == LOST_UP )
|
|
set_precision_flag_up();
|
|
else if ( FPU_bits_lost == LOST_DOWN )
|
|
set_precision_flag_down();
|
|
|
|
if ( x->exp >= EXP_OVER )
|
|
{
|
|
x->exp += EXTENDED_Ebias;
|
|
tag = arith_round_overflow(x, sign);
|
|
}
|
|
else
|
|
{
|
|
x->exp += EXTENDED_Ebias;
|
|
x->exp &= 0x7fff;
|
|
}
|
|
|
|
if ( sign != SIGN_POS )
|
|
x->exp |= 0x8000;
|
|
|
|
return tag;
|
|
|
|
}
|
|
|
|
|
|
|