blob: 025c5818bb946f77d4524dfb7161d5dacec02227 [file] [log] [blame] [raw]
/* hp2100_fp1.c: HP 2100/21MX extended-precision floating point routines
Copyright (c) 2005, J. David Bryan
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of the author shall not be
used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from the author.
Primary references:
- HP 1000 M/E/F-Series Computers Technical Reference Handbook
(5955-0282, Mar-1980)
- DOS/RTE Relocatable Library Reference Manual
(24998-90001, Oct-1981)
The extended-precision floating-point format is a 48-bit extension of the
32-bit format used for single precision. A packed "XP" number consists of a
40-bit twos-complement mantissa and an 8-bit twos-complement exponent. The
exponent is rotated left so that the sign is in the LSB. Pictorially, an XP
number appears in memory as follows:
15 14 0
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|S | mantissa high | : M
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| mantissa middle | : M + 1
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| mantissa low | exponent |XS| : M + 2
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
15 8 7 1 0
In a normalized value, the sign and MSB of the mantissa differ. Zero is
represented by all three words = 0.
Internally, an unpacked XP number is contained in a structure having a signed
64-bit mantissa and a signed 32-bit exponent. The mantissa is masked to 48
bits and left-justified, while the exponent is right-justified.
*/
#include "hp2100_defs.h"
#include "hp2100_cpu.h"
#include "hp2100_fp1.h"
#if defined (HAVE_INT64) /* we need int64 support */
/* packed starting bit numbers */
#define XP_PAD 16 /* padding LSBs */
#define XP_V_MSIGN (47 + XP_PAD) /* mantissa sign */
#define XP_V_MANT ( 8 + XP_PAD) /* mantissa */
#define XP_V_EXP ( 1 + XP_PAD) /* exponent */
#define XP_V_ESIGN ( 0 + XP_PAD) /* exponent sign */
/* packed bit widths */
#define XP_W_MSIGN 1 /* mantissa sign */
#define XP_W_MANT 39 /* mantissa */
#define XP_W_EXP 7 /* exponent */
#define XP_W_ESIGN 1 /* exponent sign */
/* packed bit masks */
#define XP_M_MSIGN (((t_uint64) 1 << XP_W_MSIGN) - 1) /* mantissa sign */
#define XP_M_MANT (((t_uint64) 1 << XP_W_MANT) - 1) /* mantissa */
#define XP_M_EXP (((t_uint64) 1 << XP_W_EXP) - 1) /* exponent */
#define XP_M_ESIGN (((t_uint64) 1 << XP_W_ESIGN) - 1) /* exponent sign */
/* packed field masks */
#define XP_MSIGN (XP_M_MSIGN << XP_V_MSIGN) /* mantissa sign */
#define XP_MANT (XP_M_MANT << XP_V_MANT) /* mantissa */
#define XP_SMANT (XP_MSIGN | XP_MANT) /* signed mantissa */
/* unpacked starting bit numbers */
#define XP_V_UMANT (0 + XP_PAD) /* signed mantissa */
/* unpacked bit widths */
#define XP_W_USMANT 48 /* signed mantissa */
/* unpacked bit masks */
#define XP_M_USMANT (((t_uint64) 1 << XP_W_USMANT) - 1) /* mantissa */
/* unpacked field masks */
#define XP_USMANT (XP_M_USMANT << XP_V_UMANT) /* signed mantissa */
/* values */
#define XP_ONEHALF ((t_int64) 1 << (XP_V_MSIGN - 1)) /* mantissa = 0.5 */
#define XP_HALSBPOS ((t_int64) 1 << (XP_V_MANT - 1)) /* + 1/2 LSB */
#define XP_HALSBNEG ((t_int64) XP_HALSBPOS - 1) /* - 1/2 LSB */
#define XP_MAXPOS ((t_int64) XP_MANT) /* maximum pos mantissa */
#define XP_MAXNEG ((t_int64) XP_MSIGN) /* maximum neg mantissa */
#define XP_MAXEXP ((t_int64) XP_M_EXP) /* maximum pos exponent */
/* helpers */
#define DENORM(x) ((((x) ^ (x) << 1) & XP_MSIGN) == 0)
#define TO_INT64(xpn) ((t_int64) ((t_uint64) (xpn).high << 32 | (xpn).low))
/* internal unpacked extended-precision representation */
typedef struct {
t_int64 mantissa;
int32 exponent;
} XPU;
/* Private routines */
/* Pack an unpacked mantissa and exponent */
static XPN to_xpn (t_uint64 m, int32 e)
{
XPN packed;
packed.high = (uint32) (m >> 32);
packed.low = (uint32) (m | ((e & XP_M_EXP) << XP_V_EXP) | ((e < 0) << XP_V_ESIGN));
return packed;
}
/* Unpack a packed number */
static XPU unpack (XPN packed)
{
XPU unpacked;
unpacked.mantissa = TO_INT64 (packed) & XP_SMANT; /* left-justify mantissa */
unpacked.exponent = (int8) ((packed.low >> XP_V_EXP & XP_M_EXP) | /* sign-extend exponent */
packed.low >> (XP_V_ESIGN - XP_W_EXP));
return unpacked;
}
/* Normalize an unpacked number */
static void normalize (XPU *unpacked)
{
if (unpacked->mantissa) { /* non-zero? */
while (DENORM (unpacked->mantissa)) { /* normal form? */
unpacked->exponent = unpacked->exponent - 1; /* no, so shift */
unpacked->mantissa = unpacked->mantissa << 1;
}
}
else unpacked->exponent = 0; /* clean for zero */
return;
}
/* Round and pack an unpacked number */
static uint32 pack (XPN *packed, XPU unpacked)
{
int32 sign, overflow = 0;
normalize (&unpacked); /* normalize */
sign = (unpacked.mantissa < 0); /* save sign */
unpacked.mantissa = (unpacked.mantissa + /* round the number */
(sign? XP_HALSBNEG: XP_HALSBPOS)) & XP_SMANT; /* mask off rounding bits */
if (sign != (unpacked.mantissa < 0)) { /* pos overflow? */
unpacked.mantissa = /* renormalize */
(t_uint64) unpacked.mantissa >> 1 & XP_SMANT; /* and remask */
unpacked.exponent = unpacked.exponent + 1;
}
else normalize (&unpacked); /* neg overflow? renorm */
unpacked.mantissa = unpacked.mantissa & XP_SMANT;
if (unpacked.mantissa == 0) /* result 0? */
packed->high = packed->low = 0; /* return 0 */
else if (unpacked.exponent < -(XP_MAXEXP + 1)) { /* underflow? */
packed->high = packed->low = 0; /* return 0 */
overflow = 1; /* and set overflow */
}
else if (unpacked.exponent > XP_MAXEXP) { /* overflow? */
if (sign) *packed = to_xpn (XP_MAXNEG, XP_MAXEXP); /* return neg infinity */
else *packed = to_xpn (XP_MAXPOS, XP_MAXEXP); /* or pos infinity */
overflow = 1; /* with overflow */
}
else *packed = to_xpn (unpacked.mantissa, unpacked.exponent);
return overflow;
}
/* Complement an unpacked number */
static void complement (XPU *result)
{
result->mantissa = -result->mantissa; /* negate mantissa */
if (result->mantissa == XP_MAXNEG) { /* maximum negative? */
result->mantissa = (t_uint64) result->mantissa >> 1; /* renormalize to pos */
result->exponent = result->exponent + 1; /* correct exponent */
}
return;
}
/* Add two unpacked numbers
The mantissas are first aligned (if necessary) by scaling the smaller of the
two operands. If the magnitude of the difference between the exponents is
greater than the number of significant bits, then the smaller number has been
scaled to zero, and so the sum is simply the larger operand. Otherwise, the
sum is computed and checked for overflow, which has occured if the signs of
the operands are the same but differ from that of the result. Scaling and
renormalization is perfomed if overflow occurred. */
static void add (XPU *sum, XPU augend, XPU addend)
{
int32 magn;
if (augend.mantissa == 0) *sum = addend; /* x + 0 = x */
else if (addend.mantissa == 0) *sum = augend; /* 0 + x = x */
else {
magn = augend.exponent - addend.exponent; /* difference exponents */
if (magn > 0) { /* addend smaller? */
*sum = augend; /* preset augend */
addend.mantissa = addend.mantissa >> magn; /* align addend */
}
else { /* augend smaller? */
*sum = addend; /* preset addend */
magn = -magn; /* make difference positive */
augend.mantissa = augend.mantissa >> magn; /* align augend */
}
if (magn <= XP_W_MANT + 1) { /* check mangitude */
sum->mantissa = addend.mantissa + augend.mantissa; /* add mantissas */
if (((addend.mantissa < 0) == (augend.mantissa < 0)) && /* chk overflow */
((addend.mantissa < 0) != (sum->mantissa < 0))) {
sum->mantissa = (addend.mantissa & XP_MSIGN) | /* restore sign */
(t_uint64) sum->mantissa >> 1; /* renormalize */
sum->exponent = sum->exponent + 1; /* adjust exponent */
}
}
}
return;
}
/* Multiply two unpacked numbers
The firmware first negates the operands as necessary so that the values are
positive. Then it performs six of the nine 16-bit x 16-bit = 32-bit unsigned
multiplications required for a full 96-bit product. Given a 48-bit
multiplicand "a1a2a3" and a 48-bit multiplier "b1b2b3", the firmware performs
these calculations to develop a 48-bit product:
a1 a2 a3
+-------+-------+-------+
b1 b2 b3
+-------+-------+-------+
_________________________
a1 * b3 [m1]
+-------+-------+
a2 * b2 [m2]
+-------+-------+
a1 * b2 [m3]
+-------+-------+
a3 * b1 [m4]
+-------+-------+
a2 * b1 [m5]
+-------+-------+
a1 * b1 [m6]
+-------+-------+
_________________________________
product
+-------+-------+-------+
The least-significant words of intermediate multiplications [m1], [m2], and
[m4] are used only to develop a carry bit into the 48-bit sum. The product
is complemented if necessary to restore the sign.
Instead of implementing this algorithm directly, it is more efficient under
simulation to use 32 x 32 = 64-bit multiplications, thereby reducing the
number required from six to four. */
static void multiply (XPU *product, XPU multiplicand, XPU multiplier)
{
uint32 ah, al, bh, bl, carry, sign = 0;
t_uint64 hi, m1, m2, m3;
if ((multiplicand.mantissa == 0) || (multiplier.mantissa == 0)) /* x * 0 = 0 */
product->mantissa = product->exponent = 0;
else {
if (multiplicand.mantissa < 0) { /* negative? */
complement (&multiplicand); /* complement operand */
sign = ~sign; /* track sign */
}
if (multiplier.mantissa < 0) { /* negative? */
complement (&multiplier); /* complement operand */
sign = ~sign; /* track sign */
}
product->exponent = multiplicand.exponent + /* compute exponent */
multiplier.exponent + 1;
ah = (uint32) (multiplicand.mantissa >> 32); /* split multiplicand */
al = (uint32) multiplicand.mantissa; /* into high and low parts */
bh = (uint32) (multiplier.mantissa >> 32); /* split multiplier */
bl = (uint32) multiplier.mantissa; /* into high and low parts */
hi = ((t_uint64) ah * bh); /* form four cross products */
m1 = ((t_uint64) ah * bl); /* using 32 x 32 = 64-bit */
m2 = ((t_uint64) al * bh); /* hardware multiplies */
m3 = ((t_uint64) al * bl);
carry = ((uint32) m1 + (uint32) m2 + /* form a carry bit */
(uint32) (m3 >> 32)) >> (31 - XP_V_UMANT); /* and align to LSB - 1 */
product->mantissa = (hi + (m1 >> 32) + /* align, sum, and mask */
(m2 >> 32) + carry) & XP_USMANT;
if (sign) complement (product); /* negate if required */
}
return;
}
/* Divide two unpacked numbers
The firmware performs division by calculating (1.0 / divisor) and then
multiplying by the dividend. The simulator uses 64-bit division and uses a
"divide-and-correct" algorithm similar to the one employed by the base set
single-precision floating-point division routine. */
static void divide (XPU *quotient, XPU dividend, XPU divisor)
{
t_int64 bh, bl, m1, m2, m3, m4;
if (divisor.mantissa == 0) { /* division by zero? */
if (dividend.mantissa < 0)
quotient->mantissa = XP_MSIGN; /* return minus infinity */
else quotient->mantissa = XP_MANT; /* or plus infinity */
quotient->exponent = XP_MAXEXP + 1;
}
else if (dividend.mantissa == 0) /* dividend zero? */
quotient->mantissa = quotient->exponent = 0; /* yes; result is zero */
else {
quotient->exponent = dividend.exponent - /* division subtracts exponents */
divisor.exponent + 1;
bh = divisor.mantissa >> 32; /* split divisor */
bl = divisor.mantissa & 0xFFFFFFFF;
m1 = (dividend.mantissa >> 2) / bh; /* form 1st partial quotient */
m2 = (dividend.mantissa >> 2) % bh; /* obtain remainder */
m3 = bl * m1; /* calculate correction */
m4 = ((m2 - (m3 >> 32)) << 32) / bh; /* form 2nd partial quotient */
quotient->mantissa = (m1 << 32) + m4; /* merge quotients */
}
return;
}
#endif
/* Global routines */
/* Extended-precision memory read */
XPN ReadX (uint32 va)
{
XPN packed;
packed.high = ReadW (va) << 16 | ReadW ((va + 1) & VAMASK);
packed.low = ReadW ((va + 2) & VAMASK) << 16; /* read and pack */
return packed;
}
/* Extended-precision memory write */
void WriteX (uint32 va, XPN packed)
{
WriteW (va, (packed.high >> 16) & DMASK); /* write high word */
WriteW ((va + 1) & VAMASK, packed.high & DMASK); /* write middle word */
WriteW ((va + 2) & VAMASK, (packed.low >> 16) & DMASK); /* write low word */
}
#if defined (HAVE_INT64)
/* Extended-precision add */
uint32 x_add (XPN *sum, XPN augend, XPN addend)
{
XPU usum, uaddend, uaugend;
uaugend = unpack (augend); /* unpack augend */
uaddend = unpack (addend); /* unpack addend */
add (&usum, uaugend, uaddend); /* calculate sum */
return pack (sum, usum); /* pack sum */
}
/* Extended-precision subtract */
uint32 x_sub (XPN *difference, XPN minuend, XPN subtrahend)
{
XPU udifference, uminuend, usubtrahend;
uminuend = unpack (minuend); /* unpack minuend */
usubtrahend = unpack (subtrahend); /* unpack subtrahend */
complement (&usubtrahend); /* calculate difference */
add (&udifference, uminuend, usubtrahend); /* pack difference */
return pack (difference, udifference);
}
/* Extended-precision multiply */
uint32 x_mpy (XPN *product, XPN multiplicand, XPN multiplier)
{
XPU uproduct, umultiplicand, umultiplier;
umultiplicand = unpack (multiplicand); /* unpack multiplicand */
umultiplier = unpack (multiplier); /* unpack multiplier */
multiply (&uproduct, umultiplicand, umultiplier); /* calculate product */
return pack (product, uproduct); /* pack product */
}
/* Extended-precision divide */
uint32 x_div (XPN *quotient, XPN dividend, XPN divisor)
{
XPU uquotient, udividend, udivisor;
udividend = unpack (dividend); /* unpack dividend */
udivisor = unpack (divisor); /* unpack divisor */
divide (&uquotient, udividend, udivisor); /* calculate quotient */
return pack (quotient, uquotient); /* pack quotient */
}
/* Pack an extended-precision number
An unpacked mantissa is passed as a "packed" number with an unused exponent.
*/
uint32 x_pak (XPN *result, XPN mantissa, int32 exponent)
{
XPU unpacked;
unpacked.mantissa = TO_INT64 (mantissa); /* retrieve mantissa */
unpacked.exponent = exponent; /* and exponent */
return pack (result, unpacked); /* pack them */
}
/* Complement an extended-precision mantissa
An unpacked mantissa is passed as a "packed" number with an unused exponent.
We return the exponent increment, i.e., either zero or one, depending on
whether a renormalization was required. */
uint32 x_com (XPN *mantissa)
{
XPU unpacked;
unpacked.mantissa = TO_INT64 (*mantissa); /* retrieve mantissa */
unpacked.exponent = 0; /* exponent is irrelevant */
complement (&unpacked); /* negate it */
*mantissa = to_xpn (unpacked.mantissa, 0); /* replace mantissa */
return (uint32) unpacked.exponent; /* return exponent increment */
}
/* Complement an extended-precision number */
uint32 x_dcm (XPN *packed)
{
XPU unpacked;
unpacked = unpack (*packed); /* unpack the number */
complement (&unpacked); /* negate it */
return pack (packed, unpacked); /* and repack */
}
/* Truncate an extended-precision number */
void x_trun (XPN *result, XPN source)
{
t_uint64 mask;
uint32 bitslost;
XPU unpacked;
const XPU one = { XP_ONEHALF, 1 }; /* 0.5 * 2 ** 1 = 1.0 */
unpacked = unpack (source);
if (unpacked.exponent < 0) /* number < 0.5? */
result->high = result->low = 0; /* return 0 */
else if (unpacked.exponent > XP_W_MANT) /* no fractional bits? */
*result = source; /* already integer */
else {
mask = (XP_MANT >> unpacked.exponent) & XP_MANT; /* mask fractional bits */
bitslost = (uint32) (unpacked.mantissa & mask); /* flag if bits lost */
unpacked.mantissa = unpacked.mantissa & ~mask; /* mask off fraction */
if ((unpacked.mantissa < 0) && bitslost) /* negative? */
add (&unpacked, unpacked, one); /* truncate toward zero */
pack (result, unpacked); /* (overflow cannot occur) */
}
return;
}
#endif /* defined (HAVE_INT64) */