123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702 |
- /*
- 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 =
- {
- .exp = 0x7fff,
- .mant = { .m64 = ~0 }
- };
- const struct fp_ext fp_Inf =
- {
- .exp = 0x7fff,
- };
- /* let's start with the easy ones */
- struct fp_ext *
- fp_fabs(struct fp_ext *dest, struct fp_ext *src)
- {
- dprint(PINSTR, "fabs\n");
- 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, "fneg\n");
- 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, "fadd\n");
- 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: Implements 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, "ftst\n");
- (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, "fmul\n");
- 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, "fdiv\n");
- 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, "fsglmul\n");
- 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, "fsgldiv\n");
- 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] & 0x80000000)
- 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, "fmod\n");
- 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, "frem\n");
- return modrem_kernel(dest, src, FPCR_ROUND_RN);
- }
- struct fp_ext *
- fp_fint(struct fp_ext *dest, struct fp_ext *src)
- {
- dprint(PINSTR, "fint\n");
- 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, "fintrz\n");
- 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, "fscale\n");
- 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;
- }
|