Bochs/bochs/fpu/reg_round.c
2003-05-15 16:11:29 +00:00

555 lines
14 KiB
C

/*---------------------------------------------------------------------------+
| reg_round.c |
| $Id: reg_round.c,v 1.5 2003-05-15 16:11:29 sshwarts 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 ++;
/* if the above increment of a signed 16-bit value overflowed, substitute
the maximum positive exponent to force FPU_round to produce overflow */
if (x->exp == 0xFFFF8000)
x->exp = 0x7FFF;
}
}
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 ++;
/* if the above increment of a signed 16-bit value overflowed, substitute
the maximum positive exponent to force FPU_round to produce overflow */
if (x->exp == 0xFFFF8000)
x->exp = 0x7FFF;
}
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, inexact = 0;
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;
}
}
inexact = FPU_bits_lost;
FPU_bits_lost = 0;
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;
}
}
inexact = FPU_bits_lost;
FPU_bits_lost = 0;
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 && (x->sigh & 0x80000000) == 0)
{
/* There must be a masked underflow */
EXCEPTION(EX_Underflow);
}
if (inexact && x->exp < -16382)
{
/* There must be a masked underflow */
EXCEPTION(EX_Underflow);
}
tag = TAG_Special;
#ifndef PECULIAR_486
}
#endif
}
}
}
if (FPU_bits_lost == LOST_UP || inexact == LOST_UP)
set_precision_flag_up();
else if (FPU_bits_lost == LOST_DOWN || inexact == 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;
}