fp_arith.c
上传用户:lgb322
上传日期:2013-02-24
资源大小:30529k
文件大小:14k
- /*
- fp_arith.c: floating-point math routines for the Linux-m68k
- floating point emulator.
- Copyright (c) 1998-1999 David Huggins-Daines.
- Somewhat based on the AlphaLinux floating point emulator, by David
- Mosberger-Tang.
- You may copy, modify, and redistribute this file under the terms of
- the GNU General Public License, version 2, or any later version, at
- your convenience.
- */
- #include "fp_emu.h"
- #include "multi_arith.h"
- #include "fp_arith.h"
- const struct fp_ext fp_QNaN =
- {
- 0, 0, 0x7fff, { ~0 }
- };
- const struct fp_ext fp_Inf =
- {
- 0, 0, 0x7fff, { 0 }
- };
- /* let's start with the easy ones */
- struct fp_ext *
- fp_fabs(struct fp_ext *dest, struct fp_ext *src)
- {
- dprint(PINSTR, "fabsn");
- fp_monadic_check(dest, src);
- dest->sign = 0;
- return dest;
- }
- struct fp_ext *
- fp_fneg(struct fp_ext *dest, struct fp_ext *src)
- {
- dprint(PINSTR, "fnegn");
- fp_monadic_check(dest, src);
- dest->sign = !dest->sign;
- return dest;
- }
- /* Now, the slightly harder ones */
- /* fp_fadd: Implements the kernel of the FADD, FSADD, FDADD, FSUB,
- FDSUB, and FCMP instructions. */
- struct fp_ext *
- fp_fadd(struct fp_ext *dest, struct fp_ext *src)
- {
- int diff;
- dprint(PINSTR, "faddn");
- fp_dyadic_check(dest, src);
- if (IS_INF(dest)) {
- /* infinity - infinity == NaN */
- if (IS_INF(src) && (src->sign != dest->sign))
- fp_set_nan(dest);
- return dest;
- }
- if (IS_INF(src)) {
- fp_copy_ext(dest, src);
- return dest;
- }
- if (IS_ZERO(dest)) {
- if (IS_ZERO(src)) {
- if (src->sign != dest->sign) {
- if (FPDATA->rnd == FPCR_ROUND_RM)
- dest->sign = 1;
- else
- dest->sign = 0;
- }
- } else
- fp_copy_ext(dest, src);
- return dest;
- }
- dest->lowmant = src->lowmant = 0;
- if ((diff = dest->exp - src->exp) > 0)
- fp_denormalize(src, diff);
- else if ((diff = -diff) > 0)
- fp_denormalize(dest, diff);
- if (dest->sign == src->sign) {
- if (fp_addmant(dest, src))
- if (!fp_addcarry(dest))
- return dest;
- } else {
- if (dest->mant.m64 < src->mant.m64) {
- fp_submant(dest, src, dest);
- dest->sign = !dest->sign;
- } else
- fp_submant(dest, dest, src);
- }
- return dest;
- }
- /* fp_fsub: Implementes the kernel of the FSUB, FSSUB, and FDSUB
- instructions.
- Remember that the arguments are in assembler-syntax order! */
- struct fp_ext *
- fp_fsub(struct fp_ext *dest, struct fp_ext *src)
- {
- dprint(PINSTR, "fsub ");
- src->sign = !src->sign;
- return fp_fadd(dest, src);
- }
- struct fp_ext *
- fp_fcmp(struct fp_ext *dest, struct fp_ext *src)
- {
- dprint(PINSTR, "fcmp ");
- FPDATA->temp[1] = *dest;
- src->sign = !src->sign;
- return fp_fadd(&FPDATA->temp[1], src);
- }
- struct fp_ext *
- fp_ftst(struct fp_ext *dest, struct fp_ext *src)
- {
- dprint(PINSTR, "ftstn");
- (void)dest;
- return src;
- }
- struct fp_ext *
- fp_fmul(struct fp_ext *dest, struct fp_ext *src)
- {
- union fp_mant128 temp;
- int exp;
- dprint(PINSTR, "fmuln");
- fp_dyadic_check(dest, src);
- /* calculate the correct sign now, as it's necessary for infinities */
- dest->sign = src->sign ^ dest->sign;
- /* Handle infinities */
- if (IS_INF(dest)) {
- if (IS_ZERO(src))
- fp_set_nan(dest);
- return dest;
- }
- if (IS_INF(src)) {
- if (IS_ZERO(dest))
- fp_set_nan(dest);
- else
- fp_copy_ext(dest, src);
- return dest;
- }
- /* Of course, as we all know, zero * anything = zero. You may
- not have known that it might be a positive or negative
- zero... */
- if (IS_ZERO(dest) || IS_ZERO(src)) {
- dest->exp = 0;
- dest->mant.m64 = 0;
- dest->lowmant = 0;
- return dest;
- }
- exp = dest->exp + src->exp - 0x3ffe;
- /* shift up the mantissa for denormalized numbers,
- so that the highest bit is set, this makes the
- shift of the result below easier */
- if ((long)dest->mant.m32[0] >= 0)
- exp -= fp_overnormalize(dest);
- if ((long)src->mant.m32[0] >= 0)
- exp -= fp_overnormalize(src);
- /* now, do a 64-bit multiply with expansion */
- fp_multiplymant(&temp, dest, src);
- /* normalize it back to 64 bits and stuff it back into the
- destination struct */
- if ((long)temp.m32[0] > 0) {
- exp--;
- fp_putmant128(dest, &temp, 1);
- } else
- fp_putmant128(dest, &temp, 0);
- if (exp >= 0x7fff) {
- fp_set_ovrflw(dest);
- return dest;
- }
- dest->exp = exp;
- if (exp < 0) {
- fp_set_sr(FPSR_EXC_UNFL);
- fp_denormalize(dest, -exp);
- }
- return dest;
- }
- /* fp_fdiv: Implements the "kernel" of the FDIV, FSDIV, FDDIV and
- FSGLDIV instructions.
- Note that the order of the operands is counter-intuitive: instead
- of src / dest, the result is actually dest / src. */
- struct fp_ext *
- fp_fdiv(struct fp_ext *dest, struct fp_ext *src)
- {
- union fp_mant128 temp;
- int exp;
- dprint(PINSTR, "fdivn");
- fp_dyadic_check(dest, src);
- /* calculate the correct sign now, as it's necessary for infinities */
- dest->sign = src->sign ^ dest->sign;
- /* Handle infinities */
- if (IS_INF(dest)) {
- /* infinity / infinity = NaN (quiet, as always) */
- if (IS_INF(src))
- fp_set_nan(dest);
- /* infinity / anything else = infinity (with approprate sign) */
- return dest;
- }
- if (IS_INF(src)) {
- /* anything / infinity = zero (with appropriate sign) */
- dest->exp = 0;
- dest->mant.m64 = 0;
- dest->lowmant = 0;
- return dest;
- }
- /* zeroes */
- if (IS_ZERO(dest)) {
- /* zero / zero = NaN */
- if (IS_ZERO(src))
- fp_set_nan(dest);
- /* zero / anything else = zero */
- return dest;
- }
- if (IS_ZERO(src)) {
- /* anything / zero = infinity (with appropriate sign) */
- fp_set_sr(FPSR_EXC_DZ);
- dest->exp = 0x7fff;
- dest->mant.m64 = 0;
- return dest;
- }
- exp = dest->exp - src->exp + 0x3fff;
- /* shift up the mantissa for denormalized numbers,
- so that the highest bit is set, this makes lots
- of things below easier */
- if ((long)dest->mant.m32[0] >= 0)
- exp -= fp_overnormalize(dest);
- if ((long)src->mant.m32[0] >= 0)
- exp -= fp_overnormalize(src);
- /* now, do the 64-bit divide */
- fp_dividemant(&temp, dest, src);
- /* normalize it back to 64 bits and stuff it back into the
- destination struct */
- if (!temp.m32[0]) {
- exp--;
- fp_putmant128(dest, &temp, 32);
- } else
- fp_putmant128(dest, &temp, 31);
- if (exp >= 0x7fff) {
- fp_set_ovrflw(dest);
- return dest;
- }
- dest->exp = exp;
- if (exp < 0) {
- fp_set_sr(FPSR_EXC_UNFL);
- fp_denormalize(dest, -exp);
- }
- return dest;
- }
- struct fp_ext *
- fp_fsglmul(struct fp_ext *dest, struct fp_ext *src)
- {
- int exp;
- dprint(PINSTR, "fsglmuln");
- fp_dyadic_check(dest, src);
- /* calculate the correct sign now, as it's necessary for infinities */
- dest->sign = src->sign ^ dest->sign;
- /* Handle infinities */
- if (IS_INF(dest)) {
- if (IS_ZERO(src))
- fp_set_nan(dest);
- return dest;
- }
- if (IS_INF(src)) {
- if (IS_ZERO(dest))
- fp_set_nan(dest);
- else
- fp_copy_ext(dest, src);
- return dest;
- }
- /* Of course, as we all know, zero * anything = zero. You may
- not have known that it might be a positive or negative
- zero... */
- if (IS_ZERO(dest) || IS_ZERO(src)) {
- dest->exp = 0;
- dest->mant.m64 = 0;
- dest->lowmant = 0;
- return dest;
- }
- exp = dest->exp + src->exp - 0x3ffe;
- /* do a 32-bit multiply */
- fp_mul64(dest->mant.m32[0], dest->mant.m32[1],
- dest->mant.m32[0] & 0xffffff00,
- src->mant.m32[0] & 0xffffff00);
- if (exp >= 0x7fff) {
- fp_set_ovrflw(dest);
- return dest;
- }
- dest->exp = exp;
- if (exp < 0) {
- fp_set_sr(FPSR_EXC_UNFL);
- fp_denormalize(dest, -exp);
- }
- return dest;
- }
- struct fp_ext *
- fp_fsgldiv(struct fp_ext *dest, struct fp_ext *src)
- {
- int exp;
- unsigned long quot, rem;
- dprint(PINSTR, "fsgldivn");
- fp_dyadic_check(dest, src);
- /* calculate the correct sign now, as it's necessary for infinities */
- dest->sign = src->sign ^ dest->sign;
- /* Handle infinities */
- if (IS_INF(dest)) {
- /* infinity / infinity = NaN (quiet, as always) */
- if (IS_INF(src))
- fp_set_nan(dest);
- /* infinity / anything else = infinity (with approprate sign) */
- return dest;
- }
- if (IS_INF(src)) {
- /* anything / infinity = zero (with appropriate sign) */
- dest->exp = 0;
- dest->mant.m64 = 0;
- dest->lowmant = 0;
- return dest;
- }
- /* zeroes */
- if (IS_ZERO(dest)) {
- /* zero / zero = NaN */
- if (IS_ZERO(src))
- fp_set_nan(dest);
- /* zero / anything else = zero */
- return dest;
- }
- if (IS_ZERO(src)) {
- /* anything / zero = infinity (with appropriate sign) */
- fp_set_sr(FPSR_EXC_DZ);
- dest->exp = 0x7fff;
- dest->mant.m64 = 0;
- return dest;
- }
- exp = dest->exp - src->exp + 0x3fff;
- dest->mant.m32[0] &= 0xffffff00;
- src->mant.m32[0] &= 0xffffff00;
- /* do the 32-bit divide */
- if (dest->mant.m32[0] >= src->mant.m32[0]) {
- fp_sub64(dest->mant, src->mant);
- fp_div64(quot, rem, dest->mant.m32[0], 0, src->mant.m32[0]);
- dest->mant.m32[0] = 0x80000000 | (quot >> 1);
- dest->mant.m32[1] = (quot & 1) | rem; /* only for rounding */
- } else {
- fp_div64(quot, rem, dest->mant.m32[0], 0, src->mant.m32[0]);
- dest->mant.m32[0] = quot;
- dest->mant.m32[1] = rem; /* only for rounding */
- exp--;
- }
- if (exp >= 0x7fff) {
- fp_set_ovrflw(dest);
- return dest;
- }
- dest->exp = exp;
- if (exp < 0) {
- fp_set_sr(FPSR_EXC_UNFL);
- fp_denormalize(dest, -exp);
- }
- return dest;
- }
- /* fp_roundint: Internal rounding function for use by several of these
- emulated instructions.
- This one rounds off the fractional part using the rounding mode
- specified. */
- static void fp_roundint(struct fp_ext *dest, int mode)
- {
- union fp_mant64 oldmant;
- unsigned long mask;
- if (!fp_normalize_ext(dest))
- return;
- /* infinities and zeroes */
- if (IS_INF(dest) || IS_ZERO(dest))
- return;
- /* first truncate the lower bits */
- oldmant = dest->mant;
- switch (dest->exp) {
- case 0 ... 0x3ffe:
- dest->mant.m64 = 0;
- break;
- case 0x3fff ... 0x401e:
- dest->mant.m32[0] &= 0xffffffffU << (0x401e - dest->exp);
- dest->mant.m32[1] = 0;
- if (oldmant.m64 == dest->mant.m64)
- return;
- break;
- case 0x401f ... 0x403e:
- dest->mant.m32[1] &= 0xffffffffU << (0x403e - dest->exp);
- if (oldmant.m32[1] == dest->mant.m32[1])
- return;
- break;
- default:
- return;
- }
- fp_set_sr(FPSR_EXC_INEX2);
- /* We might want to normalize upwards here... however, since
- we know that this is only called on the output of fp_fdiv,
- or with the input to fp_fint or fp_fintrz, and the inputs
- to all these functions are either normal or denormalized
- (no subnormals allowed!), there's really no need.
- In the case of fp_fdiv, observe that 0x80000000 / 0xffff =
- 0xffff8000, and the same holds for 128-bit / 64-bit. (i.e. the
- smallest possible normal dividend and the largest possible normal
- divisor will still produce a normal quotient, therefore, (normal
- << 64) / normal is normal in all cases) */
- switch (mode) {
- case FPCR_ROUND_RN:
- switch (dest->exp) {
- case 0 ... 0x3ffd:
- return;
- case 0x3ffe:
- /* As noted above, the input is always normal, so the
- guard bit (bit 63) is always set. therefore, the
- only case in which we will NOT round to 1.0 is when
- the input is exactly 0.5. */
- if (oldmant.m64 == (1ULL << 63))
- return;
- break;
- case 0x3fff ... 0x401d:
- mask = 1 << (0x401d - dest->exp);
- if (!(oldmant.m32[0] & mask))
- return;
- if (oldmant.m32[0] & (mask << 1))
- break;
- if (!(oldmant.m32[0] << (dest->exp - 0x3ffd)) &&
- !oldmant.m32[1])
- return;
- break;
- case 0x401e:
- if (!(oldmant.m32[1] >= 0))
- return;
- if (oldmant.m32[0] & 1)
- break;
- if (!(oldmant.m32[1] << 1))
- return;
- break;
- case 0x401f ... 0x403d:
- mask = 1 << (0x403d - dest->exp);
- if (!(oldmant.m32[1] & mask))
- return;
- if (oldmant.m32[1] & (mask << 1))
- break;
- if (!(oldmant.m32[1] << (dest->exp - 0x401d)))
- return;
- break;
- default:
- return;
- }
- break;
- case FPCR_ROUND_RZ:
- return;
- default:
- if (dest->sign ^ (mode - FPCR_ROUND_RM))
- break;
- return;
- }
- switch (dest->exp) {
- case 0 ... 0x3ffe:
- dest->exp = 0x3fff;
- dest->mant.m64 = 1ULL << 63;
- break;
- case 0x3fff ... 0x401e:
- mask = 1 << (0x401e - dest->exp);
- if (dest->mant.m32[0] += mask)
- break;
- dest->mant.m32[0] = 0x80000000;
- dest->exp++;
- break;
- case 0x401f ... 0x403e:
- mask = 1 << (0x403e - dest->exp);
- if (dest->mant.m32[1] += mask)
- break;
- if (dest->mant.m32[0] += 1)
- break;
- dest->mant.m32[0] = 0x80000000;
- dest->exp++;
- break;
- }
- }
- /* modrem_kernel: Implementation of the FREM and FMOD instructions
- (which are exactly the same, except for the rounding used on the
- intermediate value) */
- static struct fp_ext *
- modrem_kernel(struct fp_ext *dest, struct fp_ext *src, int mode)
- {
- struct fp_ext tmp;
- fp_dyadic_check(dest, src);
- /* Infinities and zeros */
- if (IS_INF(dest) || IS_ZERO(src)) {
- fp_set_nan(dest);
- return dest;
- }
- if (IS_ZERO(dest) || IS_INF(src))
- return dest;
- /* FIXME: there is almost certainly a smarter way to do this */
- fp_copy_ext(&tmp, dest);
- fp_fdiv(&tmp, src); /* NOTE: src might be modified */
- fp_roundint(&tmp, mode);
- fp_fmul(&tmp, src);
- fp_fsub(dest, &tmp);
- /* set the quotient byte */
- fp_set_quotient((dest->mant.m64 & 0x7f) | (dest->sign << 7));
- return dest;
- }
- /* fp_fmod: Implements the kernel of the FMOD instruction.
- Again, the argument order is backwards. The result, as defined in
- the Motorola manuals, is:
- fmod(src,dest) = (dest - (src * floor(dest / src))) */
- struct fp_ext *
- fp_fmod(struct fp_ext *dest, struct fp_ext *src)
- {
- dprint(PINSTR, "fmodn");
- return modrem_kernel(dest, src, FPCR_ROUND_RZ);
- }
- /* fp_frem: Implements the kernel of the FREM instruction.
- frem(src,dest) = (dest - (src * round(dest / src)))
- */
- struct fp_ext *
- fp_frem(struct fp_ext *dest, struct fp_ext *src)
- {
- dprint(PINSTR, "fremn");
- return modrem_kernel(dest, src, FPCR_ROUND_RN);
- }
- struct fp_ext *
- fp_fint(struct fp_ext *dest, struct fp_ext *src)
- {
- dprint(PINSTR, "fintn");
- fp_copy_ext(dest, src);
- fp_roundint(dest, FPDATA->rnd);
- return dest;
- }
- struct fp_ext *
- fp_fintrz(struct fp_ext *dest, struct fp_ext *src)
- {
- dprint(PINSTR, "fintrzn");
- fp_copy_ext(dest, src);
- fp_roundint(dest, FPCR_ROUND_RZ);
- return dest;
- }
- struct fp_ext *
- fp_fscale(struct fp_ext *dest, struct fp_ext *src)
- {
- int scale, oldround;
- dprint(PINSTR, "fscalen");
- fp_dyadic_check(dest, src);
- /* Infinities */
- if (IS_INF(src)) {
- fp_set_nan(dest);
- return dest;
- }
- if (IS_INF(dest))
- return dest;
- /* zeroes */
- if (IS_ZERO(src) || IS_ZERO(dest))
- return dest;
- /* Source exponent out of range */
- if (src->exp >= 0x400c) {
- fp_set_ovrflw(dest);
- return dest;
- }
- /* src must be rounded with round to zero. */
- oldround = FPDATA->rnd;
- FPDATA->rnd = FPCR_ROUND_RZ;
- scale = fp_conv_ext2long(src);
- FPDATA->rnd = oldround;
- /* new exponent */
- scale += dest->exp;
- if (scale >= 0x7fff) {
- fp_set_ovrflw(dest);
- } else if (scale <= 0) {
- fp_set_sr(FPSR_EXC_UNFL);
- fp_denormalize(dest, -scale);
- } else
- dest->exp = scale;
- return dest;
- }