/*---------------------------------------------------------------------------+ | 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; }