Bochs/bochs/fpu/reg_round.c

540 lines
13 KiB
C
Raw Normal View History

/*---------------------------------------------------------------------------+
| reg_round.c |
2001-10-06 07:53:46 +04:00
| $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;
}