bl791's picture
download
raw
81.4 kB
! ----------------------------------------------------------------------------
! FP: Core of floating point library
!
! Supplied for use with Inform 6 Serial number 020326
! Release 1/2
! (c) Kevin Bracey 2002
! but freely usable (see manuals)
! ----------------------------------------------------------------------------
System_file;
Constant FE_INVALID = $01;
Constant FE_DIVBYZERO = $02;
Constant FE_OVERFLOW = $04;
Constant FE_UNDERFLOW = $08;
Constant FE_INEXACT = $10;
Constant FE_ALL_EXCEPT = $1F;
Class FEnv
with rounding FE_TONEAREST,
status 0,
printmode FE_PRINT_G,
printprecision 6,
inx_handler [; print "[** Floating-point trap: Inexact **]^"; quit; ],
ufl_handler [; print "[** Floating-point trap: Underflow **]^"; quit; ],
ofl_handler [; print "[** Floating-point trap: Overflow **]^"; quit; ],
ivo_handler [; print "[** Floating-point trap: Invalid operation **]^"; quit; ],
dvz_handler [; print "[** Floating-point trap: Division by zero **]^"; quit; ];
FEnv activefenv;
! Rounding modes
Constant FE_TONEAREST = 1;
Constant FE_UPWARD = 2;
Constant FE_DOWNWARD = 3;
Constant FE_TOWARDZERO = 4;
! Invalid operation reasons, stored in NaNs and passed to trap handlers
Constant InvReason_SigNaN = 0;
Constant InvReason_InitNaN = 1;
Constant InvReason_MagSubInf = 4;
Constant InvReason_InfTimes0 = 5;
Constant InvReason_0TimesInf = 6;
Constant InvReason_0Div0 = 7;
Constant InvReason_InfDivInf = 8;
Constant InvReason_InfRemX = 9;
Constant InvReason_XRem0 = 10;
Constant InvReason_SqrtNeg = 11;
Constant InvReason_FixQNaN = 12;
Constant InvReason_FixInf = 13;
Constant InvReason_FixRange = 14;
Constant InvReason_CompQNaN = 15;
! Destination format specifiers passed to trap handlers
Constant FE_FMT_S = 0; ! Single
Constant FE_FMT_X = 1; ! Extended
Constant FE_FMT_P = 2; ! Packed decimal
Constant FE_FMT_I = 3; ! Integer
Constant FE_FMT_N = 4; ! None
! Operation codes passed to trap handlers
Constant FE_OP_ADD = 0;
Constant FE_OP_SUB = 1;
Constant FE_OP_MUL = 2;
Constant FE_OP_DIV = 3;
Constant FE_OP_REM = 4;
Constant FE_OP_CMP = 5;
Constant FE_OP_CMPE = 6;
Constant FE_OP_CONV = 7;
Constant FE_OP_DEC = 8;
Constant FE_OP_FIX = 9;
Constant FE_OP_FLT = 10;
Constant FE_OP_RND = 11;
Constant FE_OP_SQRT = 12;
Constant FE_OP_RAISE = 13;
! Decimal printing format
Constant FE_PRINT_G = 0;
Constant FE_PRINT_E = 1;
Constant FE_PRINT_F = 2;
! Return values from fcmp[e][x]
Constant FCMP_U = $8;
Constant FCMP_L = $4;
Constant FCMP_E = $2;
Constant FCMP_G = $1;
! Global state variables
Global fpstatus; ! Working copy of activefenv.fpstatus (for speed)
! Internal workings
Array _fpscratch --> 6;
Array _workF0 --> 3;
Array _workF1 --> 3;
Global _precision;
Global _dest;
Global _op;
Global _rounding;
Constant INXE = $1000;
Constant UFLE = $0800;
Constant OFLE = $0400;
Constant DVZE = $0200;
Constant IVOE = $0100;
#Ifndef NULL;
Constant NULL = $FFFF;
#Endif;
! Internal utility functions
[ _UCmp x y;
@add x $8000 -> x;
@add y $8000 -> y;
@jg x y ?rtrue;
@jl x y ?~rfalse;
@ret -1;
];
[ _mul32 dest ah bh
al bl m1 m2 tmp;
! Split a and b into two 8-bit halves
@and ah $00FF -> al;
@log_shift ah 0-8 -> ah;
@and bh $00FF -> bl;
@log_shift bh 0-8 -> bh;
! Four 8x8->16 multiplies
m1 = al * bh; ! ( 0 m1h, m1l 0 )
m2 = ah * bl; ! + ( 0 m2h, m2l 0 )
ah = ah * bh; ! + ( ah , 0 )
al = al * bl; ! + ( 0 , al )
! Add m1 into (ah,al)
@log_shift m1 8 -> tmp;
al = al + tmp;
if (_UCmp(al,tmp)<0) ah++;
@log_shift m1 0-8 -> tmp;
ah = ah + tmp;
! Add m2 into (ah,al)
@log_shift m2 8 -> tmp;
al = al + tmp;
if (_UCmp(al,tmp)<0) ah++;
@log_shift m2 0-8 -> tmp;
ah = ah + tmp;
dest-->0 = ah;
dest-->1 = al;
];
! Clear specified status flags
[ feclearexcept excepts;
excepts = excepts & FE_ALL_EXCEPT;
fpstatus = fpstatus &~ excepts;
];
! Set specified status flags
[ fesetexcept excepts;
excepts = excepts & FE_ALL_EXCEPT;
fpstatus = fpstatus | excepts;
];
! Raise specified floating point exceptions
[ feraiseexcept excepts
round;
round = fegetround();
if (excepts & FE_INVALID)
{
if (fpstatus & IVOE)
activefenv.ivo_handler(NULL, FE_FMT_N, FE_OP_RAISE, round, InvReason_InitNaN);
else
fpstatus = fpstatus | FE_INVALID;
}
if (excepts & FE_DIVBYZERO)
{
if (fpstatus & DVZE)
activefenv.dvz_handler(NULL, FE_FMT_N, FE_OP_RAISE, round);
else
fpstatus = fpstatus | FE_DIVBYZERO;
}
if (excepts & FE_OVERFLOW)
{
if (fpstatus & OFLE)
activefenv.ofl_handler(NULL, FE_FMT_N, FE_OP_RAISE, round);
else
fpstatus = fpstatus | FE_OVERFLOW;
}
if (excepts & FE_UNDERFLOW)
{
if (fpstatus & UFLE)
activefenv.ufl_handler(NULL, FE_FMT_N, FE_OP_RAISE, round);
else
fpstatus = fpstatus | FE_UNDERFLOW;
}
if (excepts & FE_INEXACT)
{
if (fpstatus & INXE)
activefenv.inx_handler(NULL, FE_FMT_N, FE_OP_RAISE, round);
else
fpstatus = fpstatus | FE_INEXACT;
}
];
! Test specified status flags
[ fetestexcept excepts;
excepts = excepts & FE_ALL_EXCEPT;
return fpstatus & excepts;
];
! Get current rounding mode
[ fegetround;
return activefenv.rounding;
];
! Set current rounding mode
[ fesetround round;
activefenv.rounding = round;
];
! Store current floating-point environment in envp
[ fegetenv envp;
activefenv.fpstatus = fpstatus;
FEnv.copy(envp, activefenv);
];
! Store current floating-point environment in envp, clear all exceptions
! and disable traps
[ feholdexcept envp;
fegetenv(envp);
fpstatus = 0;
];
! Restore floating-point environment from envp
[ fesetenv envp;
Fenv.copy(activefenv, envp);
fpstatus = activefenv.fpstatus;
];
! Remember exception status, restore environment, then raise the saved
! exceptions
[ feupdateenv envp
oldstatus;
oldstatus = fetestexcept(FE_ALL_EXCEPT);
fesetenv(envp);
feraiseexcept(oldstatus);
];
! Test specified trap enables
[ fetesttrap traps;
traps = traps & FE_ALL_EXCEPT;
@log_shift fpstatus 0-8 -> sp;
@and sp traps -> sp;
@ret_popped;
];
! Enable specified traps
[ feenabletrap traps;
traps = traps & FE_ALL_EXCEPT;
@log_shift traps 8 -> sp;
@or fpstatus sp -> fpstatus;
];
! Disable specified traps
[ fedisabletrap traps;
traps = traps & FE_ALL_EXCEPT;
@log_shift traps 8 -> traps;
fpstatus = fpstatus &~ traps;
];
! Set the trap handlers
[ fesettraphandler routine traps;
if (traps & FE_INVALID) activefenv.ivo_handler = routine;
if (traps & FE_DIVBYZERO) activefenv.dvz_handler = routine;
if (traps & FE_OVERFLOW) activefenv.ofl_handler = routine;
if (traps & FE_UNDERFLOW) activefenv.ufl_handler = routine;
if (traps & FE_INEXACT) activefenv.inx_handler = routine;
];
[ fegettraphandler trap;
switch (trap)
{
FE_INVALID: return activefenv.ivo_handler;
FE_DIVBYZERO: return activefenv.dvz_handler;
FE_OVERFLOW: return activefenv.ofl_handler;
FE_UNDERFLOW: return activefenv.ufl_handler;
FE_INEXACT: return activefenv.inx_handler;
default: return NULL;
}
];
[ fesetprintmode mode
oldmode;
oldmode = activefenv.printmode;
activefenv.printmode = mode;
return oldmode;
];
[ fesetprintprecision prec
oldprec;
oldprec = activefenv.printprecision;
activefenv.printprecision = prec;
return oldprec;
];
[ isnan x
h;
h = x-->0;
@test h $7F80 ?~rfalse;
@and h $007F -> sp;
@loadw x 1 -> sp;
@or sp sp -> sp;
@jz sp ?rfalse;
rtrue;
];
[ isnanx x;
@loadw x 0 -> sp;
@test sp $07FF ?~rfalse;
@loadw x 1 -> sp;
@loadw x 2 -> sp;
@or sp sp -> sp;
@jz sp ?rfalse;
rtrue;
];
[ _isnan sex mhi mlo;
@test sex $07FF ?~rfalse;
@or mhi mlo -> sp;
@jz sp ?rfalse;
rtrue;
];
[ issignalling x h;
@loadw x 0 -> h;
@and h $7FC0 -> sp;
@je sp $7F80 ?~rfalse;
@and h $007F -> sp;
@loadw x 1 -> sp;
@or sp sp -> sp;
@jz sp ?rfalse;
rtrue;
];
[ issignallingx x
h;
@loadw x 0 -> sp;
@test sp $07FF ?~rfalse;
@loadw x 1 -> h;
@loadw x 2 -> sp;
@or h sp -> sp;
@jz sp ?rfalse;
@test h $4000 ?rfalse;
rtrue;
];
[ _issignalling sex mhi mlo;
@test sex $07FF ?~rfalse;
@or mhi mlo -> sp;
@jz sp ?rfalse;
@test mhi $4000 ?rfalse;
rtrue;
];
[ isinf x;
@loadw x 1 -> sp;
@jz sp ?~rfalse;
@loadw x 0 -> sp;
@and sp $7FFF -> sp;
@je sp $7F80 ?~rfalse;
rtrue;
];
[ isinfx x;
@loadw x 1 -> sp;
@jz sp ?~rfalse;
@loadw x 2 -> sp;
@jz sp ?~rfalse;
@loadw x 0 -> sp;
@test sp $07FF ?~rfalse;
rtrue;
];
[ isfinite x;
@loadw x 0 -> sp;
@test x $7F80 ?~rtrue;
rfalse;
];
[ isfinitex x;
@loadw x 0 -> sp;
@test x $07FF ?~rtrue;
rfalse;
];
! Return values from fpclassify[x]
Constant FP_NANS = $0001;
Constant FP_NANQ = $0002;
Constant FP_ZEROM = $0004;
Constant FP_ZEROP = $0008;
Constant FP_SUBNORMALM = $0010;
Constant FP_SUBNORMALP = $0020;
Constant FP_NORMALM = $0040;
Constant FP_NORMALP = $0080;
Constant FP_INFINITYM = $0100;
Constant FP_INFINITYP = $0200;
Constant FP_NAN = FP_NANS | FP_NANQ;
Constant FP_ZERO = FP_ZEROM | FP_ZEROP;
Constant FP_SUBNORMAL = FP_SUBNORMALM | FP_SUBNORMALP;
Constant FP_NORMAL = FP_NORMALM | FP_NORMALP;
Constant FP_INFINITY = FP_INFINITYM | FP_INFINITYP;
[ fpclassify x;
return fpclassifyx(_Promote(x));
];
[ fpclassifyx x
s h l e;
s = x-->0;
h = x-->1;
l = x-->2;
e = s & $07FF;
if (e == $07FF)
{
if ((h|l) == 0)
x = FP_INFINITYM;
else if (h & $4000)
return FP_NANQ;
else
return FP_NANS;
}
else if ((h|l) == 0)
x = FP_ZEROM;
else if (h >= 0)
x = FP_SUBNORMALM;
else
x = FP_NORMALM;
if (s >= 0)
@log_shift x 1 -> x;
return x;
];
[ fcmp_common OP1 OP2
OP1emh OP2emh OP1sxm OP2sxm OP1mlo OP2mlo;
OP1sxm = OP1-->0;
OP1mlo = OP1-->1;
OP2sxm = OP2-->0 + $8000;
OP2mlo = OP2-->1;
OP1emh = OP1sxm & $7FFF;
OP2emh = OP2sxm & $7FFF;
@jg OP1emh OP2emh ?op1bigger;
@jl OP1emh OP2emh ?op2bigger;
OP1 = _UCmp(OP1mlo, OP2mlo);
@jg OP1 0 ?op1bigger;
@jz OP1 ?equalmag;
.op2bigger;
if (OP2sxm>=0) return FCMP_G; else return FCMP_L;
.op1bigger;
if (OP1sxm>=0) return FCMP_G; else return FCMP_L;
.equalmag;
if ((OP1sxm<0 && OP2sxm>=0) ||
(OP1sxm>=0 && OP2sxm<0) ||
(OP1emh|OP1mlo)==0)
return FCMP_E;
if (OP1sxm>=0) return FCMP_G; else return FCMP_L;
];
[ fcmpe OP1 OP2;
if (isnan(OP1) || isnan(OP2))
return _RaiseCmpIVO(FE_FMT_S, FE_OP_CMPE, OP1, OP2);
return fcmp_common(OP1,OP2);
];
[ fcmp OP1 OP2;
if (isnan(OP1) || isnan(OP2))
{
if (issignalling(OP1) || issignalling(OP2))
return _RaiseCmpIVO(FE_FMT_S, FE_OP_CMP, OP1, OP2);
else
return FCMP_U;
}
return fcmp_common(OP1,OP2);
];
[ fcmpx_common OP1 OP2
OP1exp OP2exp OP1sex OP2sex OP1mhi OP2mhi OP1mlo OP2mlo;
OP1sex = OP1-->0;
OP1mhi = OP1-->1;
OP1mlo = OP1-->2;
OP2sex = OP2-->0 + $8000;
OP2mhi = OP2-->1;
OP2mlo = OP2-->2;
OP1exp = OP1sex & $7FFF;
OP2exp = OP2sex & $7FFF;
@jg OP1exp OP2exp ?op1bigger;
@jl OP1exp OP2exp ?op2bigger;
OP1 = _UCmp(OP1mhi, OP2mhi);
@jg OP1 0 ?op1bigger;
@jl OP1 0 ?op2bigger;
OP1 = _UCmp(OP1mlo, OP2mlo);
@jg OP1 0 ?op1bigger;
@jz OP1 ?equalmag;
.op2bigger;
if (OP2sex>=0) return FCMP_G; else return FCMP_L;
.op1bigger;
if (OP1sex>=0) return FCMP_G; else return FCMP_L;
.equalmag;
if ((OP1sex<0 && OP2sex>=0) ||
(OP1sex>=0 && OP2sex<0) ||
(OP1exp|OP1mhi|OP1mlo)==0)
return FCMP_E;
if (OP1sex>=0) return FCMP_G; else return FCMP_L;
];
[ fcmpex OP1 OP2;
if (isnanx(OP1) || isnanx(OP2))
return _RaiseCmpIVO(FE_FMT_X, FE_OP_CMPE, OP1, OP2);
return fcmpx_common(OP1,OP2);
];
[ fcmpx OP1 OP2;
if (isnanx(OP1) || isnanx(OP2))
{
if (issignallingx(OP1) || issignallingx(OP2))
return _RaiseCmpIVO(FE_FMT_X, FE_OP_CMP, OP1, OP2);
else
return FCMP_U;
}
return fcmpx_common(OP1,OP2);
];
[ feq OP1 OP2; return fcmp (OP1,OP2) == FCMP_E; ];
[ fne OP1 OP2; return fcmp (OP1,OP2) ~= FCMP_E; ];
[ fgt OP1 OP2; return fcmpe (OP1,OP2) == FCMP_G; ];
[ fge OP1 OP2; return fcmpe (OP1,OP2) & (FCMP_G|FCMP_E); ];
[ flt OP1 OP2; return fcmpe (OP1,OP2) == FCMP_L; ];
[ fle OP1 OP2; return fcmpe (OP1,OP2) & (FCMP_L|FCMP_E); ];
[ funordered OP1 OP2; return fcmp (OP1,OP2) == FCMP_U; ];
[ flg OP1 OP2; return fcmpe (OP1,OP2) & (FCMP_L|FCMP_G); ];
[ fleg OP1 OP2; return fcmpe (OP1,OP2) ~= FCMP_U; ];
[ fug OP1 OP2; return fcmp (OP1,OP2) & (FCMP_U|FCMP_G); ];
[ fuge OP1 OP2; return fcmp (OP1,OP2) ~= FCMP_L; ];
[ ful OP1 OP2; return fcmp (OP1,OP2) & (FCMP_U|FCMP_L); ];
[ fule OP1 OP2; return fcmp (OP1,OP2) ~= FCMP_G; ];
[ fue OP1 OP2; return fcmp (OP1,OP2) & (FCMP_U|FCMP_E); ];
[ feqx OP1 OP2; return fcmpx (OP1,OP2) == FCMP_E; ];
[ fnex OP1 OP2; return fcmpx (OP1,OP2) ~= FCMP_E; ];
[ fgtx OP1 OP2; return fcmpex(OP1,OP2) == FCMP_G; ];
[ fgex OP1 OP2; return fcmpex(OP1,OP2) & (FCMP_G|FCMP_E); ];
[ fltx OP1 OP2; return fcmpex(OP1,OP2) == FCMP_L; ];
[ flex OP1 OP2; return fcmpex(OP1,OP2) & (FCMP_L|FCMP_E); ];
[ funorderedx OP1 OP2; return fcmpx (OP1,OP2) == FCMP_U; ];
[ flgx OP1 OP2; return fcmpex(OP1,OP2) & (FCMP_L|FCMP_G); ];
[ flegx OP1 OP2; return fcmpex(OP1,OP2) ~= FCMP_U; ];
[ fugx OP1 OP2; return fcmpx (OP1,OP2) & (FCMP_U|FCMP_G); ];
[ fugex OP1 OP2; return fcmpx (OP1,OP2) ~= FCMP_L; ];
[ fulx OP1 OP2; return fcmpx (OP1,OP2) & (FCMP_U|FCMP_L); ];
[ fulex OP1 OP2; return fcmpx (OP1,OP2) ~= FCMP_G; ];
[ fuex OP1 OP2; return fcmpx (OP1,OP2) & (FCMP_U|FCMP_E); ];
[ fmax dest OP1 OP2
cmp;
cmp = fcmp(OP1,OP2);
if (cmp & (FCMP_E|FCMP_G)) jump ret1;
if (cmp & FCMP_L) jump ret2;
if (isnan(OP1)) jump ret2;
.ret1; @copy_table OP1 dest 4; return;
.ret2; @copy_table OP2 dest 4; return;
];
[ fmaxx dest OP1 OP2
cmp;
cmp = fcmpx(OP1,OP2);
if (cmp & (FCMP_E|FCMP_G)) jump ret1;
if (cmp & FCMP_L) jump ret2;
if (isnanx(OP1)) jump ret2;
.ret1; @copy_table OP1 dest 6; return;
.ret2; @copy_table OP2 dest 6; return;
];
[ fmin dest OP1 OP2
cmp;
cmp = fcmp(OP1,OP2);
if (cmp & (FCMP_E|FCMP_L)) jump ret1;
if (cmp & FCMP_G) jump ret2;
if (isnan(OP1)) jump ret2;
.ret1; @copy_table OP1 dest 4; return;
.ret2; @copy_table OP2 dest 4; return;
];
[ fminx dest OP1 OP2
cmp;
cmp = fcmpx(OP1,OP2);
if (cmp & (FCMP_E|FCMP_L)) jump ret1;
if (cmp & FCMP_G) jump ret2;
if (isnanx(OP1)) jump ret2;
.ret1; @copy_table OP1 dest 6; return;
.ret2; @copy_table OP2 dest 6; return;
];
! No need for rounding as all FP formats
! can hold a 16-bit int.
[ fitos dst n;
_float(FE_FMT_S, dst, n);
];
[ fitox dst n;
_float(FE_FMT_X, dst, n);
];
[ _float prec dst n
sign exp;
_op = FE_OP_FLT;
_precision = prec;
_rounding = 0;
_dest = dst;
if (n < 0)
{
sign = $8000;
n = -n;
}
if (n == 0)
{
exp = 0;
}
else
{
n = _Normalise(1023+15, n);
exp = n-->0;
n = n-->1;
}
_RoundResult(sign, exp, n);
];
[ fstoi src rnd;
return fxtoi(_Promote(src), rnd);
];
[ fxtoi tmp rnd
OPsex OPmhi OPmlo exp mhi mlo res dir;
_rounding = rnd;
!print "fxtoi(", (fhexx) tmp, ")^";
OPsex = tmp-->0;
OPmhi = tmp-->1;
OPmlo = tmp-->2;
exp = OPsex & $07FF;
! Want to slide the binary point to the bottom
tmp = 1023 + 31 - exp;
if (tmp <= 0)
jump outofrange;
_precision = FE_FMT_X;
res = _Denorm(OPmhi, OPmlo, tmp);
mhi = res-->0;
mlo = res-->1;
tmp = res-->2;
res = _RoundNum(OPsex & $8000, exp, mhi, mlo, tmp);
mhi = res-->0;
mlo = res-->1;
dir = res-->4;
! Now have a 32-bit number in mhi, mlo
if (OPsex < 0)
{
! 2's complement it
mhi = ~mhi;
mlo = -mlo;
if (mlo == 0) ++mhi;
}
if ((mlo >= 0 && mhi ~= 0) ||
(mlo < 0 && mhi ~= -1))
jump outofrange;
if (dir)
{
if (fpstatus & INXE)
mlo = activefenv.inx_handler(mlo, FE_FMT_I, FE_OP_FIX, _rounding);
else
fpstatus = fpstatus | FE_INEXACT;
}
return mlo;
.outofrange;
return _RaiseFixIVO(OPsex, OPmhi, OPmlo);
];
[ fstox dst src noexcept
sign exp mhi mlo res;
mhi = src-->0;
mlo = src-->1;
! print "fstox(", (hex) mhi, (hex) mlo, ")=";
exp = (mhi & $7F80);
@log_shift exp 0-7 -> exp;
sign = mhi & $8000;
mhi = mhi & $007F;
@log_shift mhi 8 -> mhi;
@log_shift mlo 0-8 -> sp;
@or mhi sp -> mhi;
@log_shift mlo 8 -> mlo;
if (exp == 0)
{
if (mhi | mlo)
{
! Subnormal
res = _Normalise(1023-126, mhi, mlo);
exp = res-->0;
mhi = res-->1;
mlo = res-->2;
}
else
{
! Zero
}
}
else if (exp == $FF)
{
! Infinite or NaN
exp = $7FF;
if (mhi | mlo)
{
if ((~~noexcept) && (mhi & $4000)==0)
{
! Conversion of signalling NaN
_dest = dst;
_precision = FE_FMT_X;
_op = FE_OP_CONV;
_RaiseIVO(InvReason_SigNaN, sign | exp, mhi, mlo);
return;
}
}
}
else
{
! Normal
mhi = mhi | $8000;
exp = exp + (1023 - 127);
}
dst-->0 = sign | exp;
dst-->1 = mhi;
dst-->2 = mlo;
! print (hex) dst-->0, "|", (hex) dst-->1, (hex) dst-->2, "^";
];
[ fxtos dst src rnd
sign exp sex mhi mlo;
sex = src-->0;
mhi = src-->1;
mlo = src-->2;
!print "fstox(", (hex) mhi, (hex) mlo, ")=";
exp = sex & $07FF;
sign = sex & $8000;
_dest = dst;
_precision = FE_FMT_S;
_rounding = rnd;
_op = FE_OP_CONV;
_RoundResult(sign, exp, mhi, mlo);
];
[ fcpy dst src;
@copy_table src dst 4;
];
[ fcpyx dst src;
@copy_table src dst 6;
];
[ fneg dst src;
dst-->1 = src-->1;
dst-->0 = src-->0 + $8000;
];
[ fnegx dst src;
dst-->2 = src-->2;
dst-->1 = src-->1;
dst-->0 = src-->0 + $8000;
];
[ fabs dst src;
dst-->1 = src-->1;
dst-->0 = src-->0 & $7FFF;
];
[ fabsx dst src;
dst-->2 = src-->2;
dst-->1 = src-->1;
dst-->0 = src-->0 & $7FFF;
];
[ _Denorm h l s
w b t1 grs;
!print "Denormalising ", (hex) h, (hex) l, " by ", s, " bits^";
@log_shift s 0-4 -> w; ! words to shift
b = s & $F; ! bits to shift (0-15)
! Can't guarantee that x << 16 == 0, so must skirt around that case
if (b ~= 0)
{
t1 = 16 - b;
b = -b;
@log_shift l t1 -> grs; ! bottom b bits of l into grs
@log_shift l b -> l; ! shift l down b bits
@log_shift h t1 -> t1; ! bottom b bits of h into l
l = l | t1;
@log_shift h b -> h; ! shift h down b bits
}
if (w == 1 || w >= 3)
{
if (grs) grs = 1;
grs = l | grs;
l = h;
h = 0;
}
if (w >= 2)
{
if (grs || l) grs = 1;
grs = h | grs;
l = 0;
h = 0;
}
! print "Result is ", (hex) h, (hex) l, "/", (hex) grs; new_line;
_fpscratch-->0=h;
_fpscratch-->1=l;
_fpscratch-->2=grs;
return _fpscratch;
];
! Normalise extended precision number - deals with weird zeros, unnormalised
! operands and infinities properly. Useful for coercing the result of _Promote
! back into something suitable for user (eg trap handler) consumption.
! Assumes the parameter is representable in standard extended format without
! rounding (eg was originally a user-supplied standard or extended number).
[ _fnrmx dest OP
sex mhi mlo exp;
sex = OP-->0 & $87FF; ! strip out nasty bits (just in case)
mhi = OP-->1;
mlo = OP-->2;
exp = sex & $07FF;
@copy_table OP dest 6;
if (exp == $7FF) ! NaNs and infinities OK
{
dest-->1 = mhi & $7FFF; ! but ensure J clear
return;
}
if ((mhi|mlo)==0) ! Check for zeros
{
dest-->0 = sex & $8000; ! Ensure exponent is 0
return;
}
if (mhi < 0) ! Already normalised
return;
! Normalise it
OP = _Normalise(exp, mhi, mlo);
exp = OP-->0;
mhi = OP-->1;
mlo = OP-->2;
! If subnormal, denormalise again
if (exp < 0)
{
OP = _Denorm(mhi, mlo, -exp);
mhi = OP-->0;
mlo = OP-->1;
exp = 0;
}
dest-->0 = (sex & $8000) | exp;
dest-->1 = mhi;
dest-->2 = mlo;
];
! Normalise (mhi,mlo), by shifting bits up such that the MSB of mhi is set.
! Returns adjusted (possibly negative) exponent. (mhi,mlo) may be zero or
! already normal (in which case no change is made).
[ _Normalise exp mhi mlo
tmp;
! print "Normalising ", (hex) mhi, (hex) mlo, (char) '/', exp; new_line;
if (mhi == 0)
{
if (mlo == 0) jump out;
mhi = mlo;
mlo = 0;
exp = exp - 16;
}
if ((mhi & $FF00) == 0)
{
@log_shift mhi 8 -> mhi;
tmp = 8;
}
if ((mhi & $F000) == 0)
{
@log_shift mhi 4 -> mhi;
tmp = tmp + 4;
}
if ((mhi & $C000) == 0)
{
@log_shift mhi 2 -> mhi;
tmp = tmp + 2;
}
if (mhi >= 0)
{
mhi = mhi + mhi;
tmp ++;
}
@sub tmp 16 -> sp;
@log_shift mlo sp -> sp;
@or mhi sp -> mhi;
@log_shift mlo tmp -> mlo;
exp = exp - tmp;
.out;
! print "Result is ", (hex) mhi, (hex) mlo, (char) '/', exp; new_line;
_fpscratch-->0 = exp;
_fpscratch-->1 = mhi;
_fpscratch-->2 = mlo;
return _fpscratch;
];
[ _RoundNum RNDsgn RNDexp RNDmhi RNDmlo RNDgrs RNDdir
dir lsb;
if (_precision == FE_FMT_S)
{
if (RNDgrs) RNDgrs = 1;
@log_shift RNDmlo 8 -> sp;
@or sp RNDgrs -> RNDgrs;
RNDmlo = RNDmlo & $FF00;
lsb = $0100;
}
else
lsb = $0001;
if (_rounding == 0) _rounding = activefenv.rounding;
switch (_rounding)
{
FE_TONEAREST:
if (RNDgrs < 0) ! round (top) bit set
{
if (RNDgrs ~= $8000) ! sticky bits set
dir = 1;
else ! halfway case
{
if (RNDdir < 0 || (RNDdir == 0 && (RNDmlo & lsb)))
dir = 1;
else
dir = -1;
}
}
else if (RNDgrs > 0)
dir = -1;
FE_DOWNWARD:
if (RNDgrs)
dir = -(RNDsgn+1); ! = +32767 or -1
FE_UPWARD:
if (RNDgrs)
dir = RNDsgn + 1; ! = -32767 or +1
FE_TOWARDZERO:
if (RNDgrs)
dir = -1;
}
if (dir > 0)
{
RNDmlo = RNDmlo + lsb;
if (RNDmlo == 0)
{
if (++RNDmhi == $0) ! Mantissa overflow
{
RNDmhi = $8000;
RNDexp++;
}
}
}
! Update rounding so far
if (dir) RNDdir = dir;
_fpscratch-->0 = RNDmhi;
_fpscratch-->1 = RNDmlo;
_fpscratch-->2 = RNDexp;
_fpscratch-->3 = dir; ! direction of this rounding
_fpscratch-->4 = RNDdir;
return _fpscratch;
];
[ _ReturnResult sex mhi mlo
tmp tmp2;
if (_precision == FE_FMT_X)
{
_dest-->0 = sex;
_dest-->1 = mhi;
_dest-->2 = mlo;
}
else
{
! Simple narrowing - input should be either:
! a) finite with exponent biased for single, unnormalised if necessary
! b) infinity/NaN with exponent = $7FFF
tmp = sex & $FF;
@log_shift tmp 7 -> tmp;
mhi = mhi & $7FFF;
@log_shift mhi 0-8 -> tmp2;
_dest-->0 = (sex & $8000) | tmp | tmp2;
@log_shift mhi 8 -> mhi;
@log_shift mlo 0-8 -> mlo;
_dest-->1 = mhi | mlo;
}
];
! "Exact", normalised result provided, as extended number split into 5 parts.
! Round it to destination precision, then check for over/underflow.
! Denormalise if necessary, and store.
[ _RoundResult RNDsgn RNDexp RNDmhi RNDmlo RNDgrs RNDdir
ExpMin ExpMax BiasAdjust
res;
!print "_RoundResult(",RNDsgn, (hex)RNDexp, " ";
!print (hex) RNDmhi, (hex) RNDmlo, "|", (hex) RNDgrs, (char) ')'; new_line;
res=_RoundNum(RNDsgn, RNDexp, RNDmhi, RNDmlo, RNDgrs, RNDdir);
RNDmhi = res-->0;
RNDmlo = res-->1;
RNDexp = res-->2;
RNDdir = res-->4;
! Rebias exponent to destination format
if (_precision == FE_FMT_S)
{
RNDexp = RNDexp + 127 - 1023;
ExpMin = $01;
ExpMax = $FE;
BiasAdjust = 192;
}
else
{
ExpMin = $000;
ExpMax = $7FE;
BiasAdjust = 1536;
}
if (RNDexp < ExpMin || RNDexp > ExpMax)
{
! print "Exponent out of range^";
if (RNDexp < ExpMin)
{
! Potential underflow
if (RNDmhi | RNDmlo)
{
if (fpstatus & UFLE)
{
! Take underflow trap
if (RNDexp + BiasAdjust < ExpMin)
{
! Massive underflow
if (_precision == FE_FMT_S)
BiasAdjust = 127 - RNDexp;
else
BiasAdjust = 1023 - RNDexp;
}
BiasAdjust = -BiasAdjust;
res = activefenv.ufl_handler;
jump oflufl;
}
else
{
res = _Denorm(RNDmhi, Rndmlo, ExpMin - RNDexp);
RNDmhi = res-->0;
RNDmlo = res-->1;
RNDgrs = res-->2;
RNDexp = 0;
res = _RoundNum(RNDsgn, RNDexp, RNDmhi, RNDmlo, RNDgrs, RNDdir);
RNDmhi = res-->0;
RNDmlo = res-->1;
RNDdir = res-->4;
! Check it didn't round back up to be normalised
if (RNDmhi < 0) RNDexp = ExpMin;
if (res-->3) ! Denormalisation loss
{
fpstatus = fpstatus | FE_UNDERFLOW;
! print "Underflow (denormalisation loss)^";
}
}
}
else
RNDexp = 0;
}
else ! RNDexp > ExpMax
{
if (fpstatus & OFLE)
{
if (RNDexp - BiasAdjust > ExpMax)
{
! Massive overflow
if (_precision == FE_FMT_S)
BiasAdjust = RNDexp - 127;
else
BiasAdjust = RNDexp - 1023;
}
res = activefenv.ofl_handler;
.oflufl;
_ReturnResult(RNDsgn | (RNDexp - BiasAdjust), RNDmhi, RNDmlo);
@call_vn2 res _dest _precision _op _rounding BiasAdjust;
return;
}
else
{
fpstatus = fpstatus | FE_OVERFLOW;
res = _rounding; if (res == 0) res = fegetround();
if (res == FE_TONEAREST ||
(res == FE_UPWARD && RNDsgn >= 0) ||
(res == FE_DOWNWARD && RNDsgn < 0))
{
RNDexp = $7FF;
RNDmhi = 0;
RNDmlo = 0;
RNDdir = 1;
}
else
{
RNDexp = ExpMax;
RNDmhi = $FFFF;
RNDmlo = $FFFF;
RNDdir = -1;
}
}
}
}
!print (hex) RNDexp, (hex) RNDmhi, (hex) RNDmlo; new_line;
_ReturnResult(RNDsgn | RNDexp, RNDmhi, RNDmlo);
if (RNDdir ~= 0)
{
if (fpstatus & INXE)
activefenv.inx_handler(_dest, _precision, _op, _rounding);
else
fpstatus = fpstatus | FE_INEXACT;
}
];
! Fast, non-excepting promotion of a number from single to extended
! for internal use. Subnormal numbers will be left unnormalised,
! zeros will have unusual exponents.
[ _Promote OP
sex mhi mlo exp;
sex = OP-->0;
mlo = OP-->1;
exp = sex & $7F80;
mhi = sex & $007F;
@log_shift exp 0-7 -> exp;
@log_shift mhi 8 -> mhi;
@log_shift mlo 0-8 -> sp;
@or mhi sp -> mhi;
@log_shift mlo 8 -> mlo;
if (exp == 0)
exp = 1023-126;
else if (exp == $FF)
exp = $7FF;
else
{
exp = exp + (1023-127);
mhi = mhi | $8000;
}
_fpscratch-->0 = (sex & $8000) | exp;
_fpscratch-->1 = mhi;
_fpscratch-->2 = mlo;
return _fpscratch;
];
[ _RaiseFixIVO sex mhi mlo
reason;
if (fpstatus & IVOE)
{
if ((sex & $07FF) == $07FF)
{
if (mhi|mlo)
{
if (mhi & $4000)
reason = InvReason_FixQNaN;
else
reason = InvReason_SigNaN;
}
else
reason = InvReason_FixInf;
}
else
reason = InvReason_FixRange;
_workF0-->0 = sex; _workF0-->1 = mhi; _workF0-->2 = mlo;
_fnrmx(_workF0, _workF0);
print (frawx) _workF0; new_line;
sex = activefenv.ivo_handler;
if (_rounding == 0) _rounding = fegetround();
@call_vs2 sex 0 FE_FMT_I FE_OP_FIX _rounding reason _workF0 -> sp;
@ret_popped;
}
else
{
fpstatus = fpstatus | FE_INVALID;
! We return maximum or minimum integer, depending on sign
if (sex >= 0)
return $7FFF;
else
return $8000;
}
];
[ _RaiseCmpIVO fmt op OP1 OP2
reason;
if (fpstatus & IVOE)
{
if (fmt == FE_FMT_S) OP1 = _Promote(OP1);
_fnrmx(_workF0, OP1);
if (fmt == FE_FMT_S) OP2 = _Promote(OP2);
_fnrmx(_workF1, OP2);
if (((OP1-->1 & OP2-->1) & $4000) == 0)
reason = InvReason_SigNaN;
else
reason = InvReason_CompQNaN;
fmt = activefenv.ivo_handler;
@call_vs2 fmt 0 FE_FMT_I op 0 reason _workF0 _workF1 -> sp;
@ret_popped;
}
else
{
fpstatus = fpstatus | FE_INVALID;
return FCMP_U;
}
];
[ _RaiseIVO reason OP1sex OP1mhi OP1mlo OP2sex OP2mhi OP2mlo
tmp;
if (fpstatus & IVOE)
{
tmp = activefenv.ivo_handler;
_workF0-->0 = OP1sex; _workF0-->1 = OP1mhi; _workF0-->2 = OP1mlo;
_fnrmx(_workF0, _workF0);
@check_arg_count 7 ?haveop2;
@call_vn2 tmp _dest _precision _op _rounding reason _workF0;
return;
.haveop2;
_workF1-->0 = OP2sex; _workF1-->1 = OP2mhi; _workF1-->2 = OP2mlo;
_fnrmx(_workF1, _workF1);
if (_rounding == 0) _rounding = fegetround();
@call_vn2 tmp _dest _precision _op _rounding reason _workF0 _workF1;
}
else
{
fpstatus = fpstatus | FE_INVALID;
@log_shift reason 8 -> reason;
_ReturnResult($07FF, $4000, reason);
}
];
[ _RaiseDVZ OP1sex OP1mhi OP1mlo OP2sex
tmp;
if (fpstatus & DVZE)
{
_workF0-->0 = OP1sex; _workF0-->1 = OP1mhi; _workF0-->2 = OP1mlo;
_fnrmx(_workF0, _workF0);
_workF1-->0 = OP2sex & $8000; _workF1-->1 = $0000; _workF1-->2 = $0000;
tmp = activefenv.dvz_handler;
if (_rounding == 0) _rounding = fegetround();
@call_vn2 tmp _dest _precision _op _rounding 0 _workF0 _workF1;
}
else
{
fpstatus = fpstatus | FE_DIVBYZERO;
! Return appropriately signed infinity
_ReturnResult($07FF | ((OP1sex & $8000) + (OP2sex & $8000)));
}
];
[ _dyadic op func prec dest OP1 OP2 rnd
OP1sex OP1mhi OP1mlo OP2sex OP2mhi OP2mlo;
_op = op;
_precision = prec;
_dest = dest;
_rounding = rnd;
if (prec==0) OP1 = _Promote(OP1);
OP1sex = OP1-->0; OP1mhi = OP1-->1; OP1mlo = OP1-->2;
if (prec==0) OP2 = _Promote(OP2);
OP2sex = OP2-->0; OP2mhi = OP2-->1; OP2mlo = OP2-->2;
@test OP1sex $07FF ?uncommon;
@test OP2sex $07FF ?uncommon;
.proceed;
@call_vn2 func OP1sex OP1mhi OP1mlo OP2sex OP2mhi OP2mlo;
return;
.uncommon;
if (_issignalling(OP1sex, OP1mhi, OP1mlo) ||
_issignalling(OP2sex, OP2mhi, OP2mlo))
{
_RaiseIVO(InvReason_SigNaN, OP1sex, OP1mhi, OP1mlo,
OP2sex, OP2mhi, OP2mlo);
return;
}
else if (_isnan(OP1sex, OP1mhi, OP1mlo))
{
_ReturnResult(OP1sex, OP1mhi, OP1mlo);
return;
}
else if (_isnan(OP2sex, OP2mhi, OP2mlo))
{
_ReturnResult(OP2sex, OP2mhi, OP2mlo);
return;
}
jump proceed;
];
[ fadd dest OP1 OP2 rnd;
return _dyadic(FE_OP_ADD, _fadd, FE_FMT_S, dest, OP1, OP2, rnd);
];
[ faddx dest OP1 OP2 rnd;
_dyadic(FE_OP_ADD, _fadd, FE_FMT_X, dest, OP1, OP2, rnd);
];
[ fsub dest OP1 OP2 rnd;
_dyadic(FE_OP_SUB, _fsub, FE_FMT_S, dest, OP1, OP2, rnd);
];
[ fsubx dest OP1 OP2 rnd;
_dyadic(FE_OP_SUB, _fsub, FE_FMT_X, dest, OP1, OP2, rnd);
];
[ _fsub OP1sex OP1mhi OP1mlo OP2sex OP2mhi OP2mlo;
_fadd(OP1sex, OP1mhi, OP1mlo, OP2sex+$8000, OP2mhi, OP2mlo);
];
[ _fadd OP1sex OP1mhi OP1mlo OP2sex OP2mhi OP2mlo
RNDgrs RNDexp tmp tmp2 OP1exp OP2exp;
OP1exp = OP1sex & $07FF;
OP2exp = OP2sex & $07FF;
if (OP1exp == $7FF || OP2exp == $7FF)
{
if (OP2exp ~= $7FF) ! infinity + finite
{ _ReturnResult(OP1sex, OP1mhi, OP1mlo); return; }
if (OP1exp ~= $7FF) ! finite + infinity
{ _ReturnResult(OP2sex, OP2mhi, OP2mlo); return; }
! two infinities
if ((OP1sex & $8000) == (OP2sex & $8000))
{ _ReturnResult(OP1sex, OP1mhi, OP1mlo); return; }
else
{
if (_op == FE_OP_SUB) OP2sex = OP2sex + $8000;
_RaiseIVO(InvReason_MagSubInf, OP1sex, OP1mhi, OP1mlo,
OP2sex, OP2mhi, OP2mlo);
return;
}
}
! We let zeros and subnormals through. Algorithm will work
! fine, but we may end up with a subnormal result.
! Denormalise the smaller operand to the same exponent
! as the larger.
if (OP1exp < OP2exp)
{
RNDexp = OP2exp;
tmp = _Denorm(OP1mhi, OP1mlo, OP2exp - OP1exp);
OP1mhi = tmp-->0;
OP1mlo = tmp-->1;
tmp = tmp-->2;
tmp2 = 0;
}
else if (OP1exp > OP2exp)
{
RNDexp = OP1exp;
tmp = _Denorm(OP2mhi, OP2mlo, OP1exp - OP2exp);
OP2mhi = tmp-->0;
OP2mlo = tmp-->1;
tmp2 = tmp-->2;
tmp = 0;
}
else
{
RNDexp = OP1exp;
tmp = tmp2 = 0;
}
! Don't need original numbers any longer
OP1sex = OP1sex & $8000;
OP2sex = OP2sex & $8000;
! Now OP1sex/OP2sex = signs of OP1/OP2
! OP1mhi/OP1mlo = operand 1 mantissa
! RNDexp = prospective result exponent
! OP2mhi/OP2mlo = operand 2 mantissa
! tmp = operand 1 guard, round and sticky bits
! tmp2 = operand 2 guard, round and sticky bits
if (OP1sex == OP2sex)
{
! summation case
!print "Summing^";
!font off;
!print " ", (hex) OP1mhi, (hex) OP1mlo, (char) '/', (hex) tmp, " (", RNDexp, ")^";
!print "+ ", (hex) OP2mhi, (hex) OP2mlo, (char) '/', (hex) tmp2, "^";
!print " -------------^";
RNDgrs= tmp + tmp2; ! no carry possible - one of these is 0
OP1mlo = OP1mlo + OP2mlo;
tmp = _UCmp(OP1mlo, OP2mlo) < 0; ! get carry
tmp2 = OP1mhi + OP2mhi + tmp;
tmp = (OP1mhi < 0 && OP2mhi < 0) ||
(OP1mhi < 0 && tmp2 >= 0) ||
(OP2mhi < 0 && tmp2 >= 0);
OP1mhi = tmp2;
! print " ", tmp, (hex) OP1mhi, (hex) OP1mlo, (char) '/', (hex) RNDgrs,
! " (", RNDexp, ")^";
if (tmp)
{
@log_shift RNDgrs 1 -> tmp;
RNDgrs = RNDgrs | tmp;
@log_shift RNDgrs 0-1 -> RNDgrs;
if (OP1mlo & 1) RNDgrs = RNDgrs | $8000;
@log_shift OP1mlo 0-1 -> OP1mlo;
if (OP1mhi & 1) OP1mlo = OP1mlo | $8000;
@log_shift OP1mhi 0-1 -> OP1mhi;
OP1mhi = OP1mhi | $8000;
RNDexp = RNDexp + 1;
! print " ", (hex) OP1mhi, (hex) OP1mlo, (char) '/', (hex) RNDgrs,
! " (", RNDexp, ")^";
}
!font on;
}
else
{
!print "Difference^";
!font off;
!print " ", (hex) OP1mhi, (hex) OP1mlo, (char) '/', (hex) tmp, " (", RNDexp, ")^";
!print "- ", (hex) OP2mhi, (hex) OP2mlo, (char) '/', (hex) tmp2, "^";
!print " -------------^";
RNDgrs = tmp - tmp2;
tmp = _UCmp(tmp, tmp2) >= 0; ! Carry
tmp2 = OP1mlo - OP2mlo + tmp - 1;
tmp = (OP1mlo < 0 && OP2mlo >= 0) ||
(OP1mlo < 0 && tmp2 >= 0) ||
(OP2mlo >= 0 && tmp2 >= 0);
OP1mlo = tmp2;
tmp2 = OP1mhi - OP2mhi + tmp - 1;
tmp = (OP1mhi < 0 && OP2mhi >= 0) ||
(OP1mhi < 0 && tmp2 >= 0) ||
(OP2mhi >= 0 && tmp2 >= 0);
OP1mhi = tmp2;
! print " ", 1-tmp, (hex) OP1mhi, (hex) OP1mlo, (char) '/', (hex) RNDgrs,
! " (", RNDexp, ")^";
! If it came out negative, reverse the sign and mantissa
if (~~tmp)
{
OP1sex = OP1sex + $8000;
RNDgrs = -RNDgrs; tmp = RNDgrs == 0;
tmp2 = -OP1mlo + tmp - 1;
tmp = (OP1mlo >= 0 && tmp2 >= 0);
OP1mlo = tmp2;
OP1mhi = -OP1mhi + tmp - 1;
! print "N ", (hex) OP1mhi, (hex) OP1mlo, (char) '/', (hex) RNDgrs,
! " (", RNDexp, ")^";
}
if (OP1mhi >= 0)
{
! Need to normalise. Try a single bit at first, bringing the guard
! bit back into the mantissa.
OP1mhi = OP1mhi + OP1mhi;
if (OP1mlo < 0) OP1mhi++;
OP1mlo = OP1mlo + OP1mlo;
if (RNDgrs < 0) OP1mlo++;
RNDgrs = RNDgrs + RNDgrs;
RNDexp--;
! print " ", (hex) OP1mhi, (hex) OP1mlo, (char) '/', (hex) RNDgrs,
! " (", RNDexp, ")^";
! If still not normalised, exponent difference must have been 0 or 1,
! so round and sticky bits are both zero. Will normalise below (in
! the same code that clears up for subnormal operands).
if ((OP1mhi | OP1mlo) == 0)
{
! Zero result - sign determined by rounding mode
OP1sex = _rounding; if (OP1sex == 0) OP1sex = activefenv.rounding;
if (OP1sex == FE_DOWNWARD) OP1sex = $8000; else OP1sex = 0;
RNDexp = 0;
}
}
}
if (OP1mhi >= 0)
{
! Subnormal result
!if (RNDgrs ~= 0) print "[** Internal error: _fadd - RNDgrs @@126= 0 **]";
tmp = _Normalise(RNDexp, OP1mhi, OP1mlo);
RNDexp = tmp-->0;
OP1mhi = tmp-->1;
OP1mlo = tmp-->2;
! print " ", (hex) OP1mhi, (hex) OP1mlo, (char) '/', (hex) RNDgrs,
! " (", RNDexp, ")^";
}
!font on;
_RoundResult(OP1sex, RNDexp, OP1mhi, OP1mlo, RNDgrs);
];
[ fmul dest OP1 OP2 rnd;
_dyadic(FE_OP_MUL, _fmul, FE_FMT_S, dest, OP1, OP2, rnd);
];
[ fmulx dest OP1 OP2 rnd;
_dyadic(FE_OP_MUL, _fmul, FE_FMT_X, dest, OP1, OP2, rnd);
];
[ _fmul OP1sex OP1mhi OP1mlo OP2sex OP2mhi OP2mlo
tmp tmp2 r1 r2 r3 r4 t1 t2 c;
! Extract exponents
tmp = OP1sex & $07FF;
tmp2 = OP2sex & $07FF;
! Work out sign
c = (OP1sex & $8000) + (OP2sex & $8000);
if (tmp == $7FF || tmp2 == $7FF)
{
! Multiplication by infinity
if ((tmp2 ~= $7FF && (OP2mhi|OP2mlo) == 0) ||
(tmp ~= $7FF && (OP1mhi|OP1mlo) == 0))
{
! print "Infinity times zero^";
if (tmp == $7FF)
tmp = InvReason_InfTimes0;
else
tmp = InvReason_0TimesInf;
_RaiseIVO(tmp, OP1sex, OP1mhi, OP1mlo,
OP2sex, OP2mhi, OP2mlo);
return;
}
! Return correctly signed infinity
_ReturnResult(c | $07FF);
return;
}
OP1sex = c;
if (OP1mhi >= 0)
{
r1 = _Normalise(tmp, OP1mhi, OP1mlo);
tmp = r1-->0;
OP1mhi = r1-->1;
OP1mlo = r1-->2;
}
if (OP2mhi >= 0)
{
r1 = _Normalise(tmp2, OP2mhi, OP2mlo);
tmp2 = r1-->0;
OP2mhi = r1-->1;
OP2mlo = r1-->2;
}
if ((OP1mhi&OP2mhi) == 0)
jump multzeros;
OP2sex = tmp + tmp2 - 1022;
!print (hex) OP1mhi, (hex) OP1mlo, " x ", (hex) OP2mhi, (hex) OP2mlo;
! new_line;
! OP1mhi * OP2mhi -> (r1,r2)
_mul32(_fpscratch, OP1mhi, OP2mhi);
r1 = _fpscratch-->0;
r2 = _fpscratch-->1;
if (OP1mlo ~= 0 && OP2mlo ~= 0)
{
! OP1mlo * OP2mlo -> (r3,r4)
_mul32(_fpscratch, OP1mlo, OP2mlo);
r3 = _fpscratch-->0;
r4 = _fpscratch-->1;
}
if (OP2mlo ~= 0)
{
! OP1mhi * OP2mlo -> (t1, t2)
_mul32(_fpscratch, OP1mhi, OP2mlo);
t1 = _fpscratch-->0;
t2 = _fpscratch-->1;
! Add ( 0, t1, t2, 0)
! to (r1, r2, r3, r4)
r3 = r3 + t2;
c = _UCmp(r3, t2) < 0;
tmp = r2 + t1 + c;
if ((r2 < 0 && t1 < 0) || (r2 < 0 && tmp >= 0) || (t1 < 0 && tmp >= 0))
r1++;
r2 = tmp;
}
if (OP1mlo ~= 0)
{
! OP1mlo * OP2mhi -> (t1, t2)
_mul32(_fpscratch, OP1mlo, OP2mhi);
t1 = _fpscratch-->0;
t2 = _fpscratch-->1;
! Add ( 0, t1, t2, 0)
! to (r1, r2, r3, r4)
r3 = r3 + t2;
c = _UCmp(r3, t2) < 0;
tmp = r2 + t1 + c;
if ((r2 < 0 && t1 < 0) || (r2 < 0 && tmp >= 0) || (t1 < 0 && tmp >= 0))
r1++;
r2 = tmp;
}
! font off;
!print " ", (hex) r1, (hex) r2, (hex) r3, (hex) r4, " (", OP2sex, ")^";
! Make up guard, round and sticky bits:
@log_shift r4 2 -> sp;
@or r4 sp -> r4;
@log_shift r4 0-2 -> sp;
@or r3 sp -> r3;
!print " ", (hex) r1, (hex) r2, "|", (hex) r3, " (", OP2sex, ")^";
if (r1 >= 0)
{
! Renormalise, recovering the guard bit.
r1 = r1 + r1;
if (r2 < 0) ++r1;
r2 = r2 + r2;
if (r3 < 0) ++r2;
r3 = r3 + r3;
--OP2sex;
! print " ", (hex) r1, (hex) r2, "|", (hex) r3, " (", OP2sex, ")^";
}
! font on;
_RoundResult(OP1sex, OP2sex, r1, r2, r3);
return;
.multzeros;
_RoundResult(OP1sex);
];
[ fdiv dest OP1 OP2 rnd;
_dyadic(FE_OP_DIV, _fdiv, FE_FMT_S, dest, OP1, OP2, rnd);
];
[ fdivx dest OP1 OP2 rnd;
_dyadic(FE_OP_DIV, _fdiv, FE_FMT_X, dest, OP1, OP2, rnd);
];
[ _fdiv OP1sex OP1mhi OP1mlo OP2sex OP2mhi OP2mlo
c RNDexp RNDsgn Qmhi Qmmi Qmlo bits reqbits;
! Extract exponents
Qmhi = OP1sex & $07FF;
Qmlo = OP2sex & $07FF;
! Work out sign
RNDsgn = (OP1sex & $8000) + (OP2sex & $8000);
if (Qmhi == $7FF || Qmlo == $7FF)
{
if (Qmlo ~= $7FF)
{
! Infinity / x
! Return correctly signed infinity
_ReturnResult(RNDsgn | $07FF);
}
else if (Qmhi ~= $7FF)
{
! x / Infinity
! Return correctly signed zero
_ReturnResult(RNDsgn);
}
else
{
! Infinity / Infinity
_RaiseIVO(InvReason_InfDivInf, OP1sex, OP1mhi, OP1mlo,
OP2sex, OP2mhi, OP2mlo);
}
return;
}
if ((OP1mhi|OP1mlo)==0)
{
if ((OP2mhi|OP2mlo)==0)
! Zero / Zero
_RaiseIVO(InvReason_0Div0, OP1sex, OP1mhi, OP1mlo,
OP2sex, OP2mhi, OP2mlo);
else
! Zero / X
_ReturnResult(RNDsgn);
return;
}
if ((OP2mhi|OP2mlo)==0)
{
! X / 0
_RaiseDVZ(OP1sex, OP1mhi, OP1mlo, OP2sex);
return;
}
if (OP1mhi >= 0)
{
c = _Normalise(Qmhi, OP1mhi, OP1mlo);
Qmhi = c-->0;
OP1mhi = c-->1;
OP1mlo = c-->2;
}
if (OP2mhi >= 0)
{
c = _Normalise(Qmlo, OP2mhi, OP2mlo);
Qmlo = c-->0;
OP2mhi = c-->1;
OP2mlo = c-->2;
}
! Prospective exponent
RNDexp = Qmhi - Qmlo + 1023;
! A basic long division algorithm.
! (Qmhi,Qmmi,Qmlo) will be the quotient
! (c,OP1mhi,OP1mlo) is the dividend (c using 1 bit only)
if (_precision == FE_FMT_S)
reqbits = 24 + 2; ! + 2 for guard + round
else
reqbits = 32 + 2;
c=0;
Qmhi=0;
Qmmi=0;
Qmlo=0;
! font off;
for (bits = 0: bits < reqbits && (c|OP1mhi|OP1mlo): bits++)
{
! print c, (hex) OP1mhi, (hex) OP1mlo, " ", (hex) OP2mhi, (hex) OP2mlo;
Qmhi = Qmhi + Qmhi; if (Qmmi < 0) ++Qmhi;
Qmmi = Qmmi + Qmmi; if (Qmlo < 0) ++Qmmi;
Qmlo = Qmlo + Qmlo;
if (c || _UCmp(OP2mhi, OP1mhi) < 0 ||
(OP2mhi == OP1mhi && _UCmp(OP2mlo, OP1mlo) <= 0))
{
Qmlo = Qmlo | 1;
c = _UCmp(OP1mlo, OP2mlo) >= 0;
OP1mlo = OP1mlo - OP2mlo;
OP1mhi = OP1mhi - OP2mhi + c - 1;
c = 0;
}
! print " ", (hex) Qmhi, (hex) Qmmi, (hex) Qmlo; new_line;
c = OP1mhi < 0;
OP1mhi = OP1mhi + OP1mhi; if (OP1mlo<0) ++OP1mhi;
OP1mlo = OP1mlo + OP1mlo;
}
bits = 48 - bits;
while (bits >= 16)
{
Qmhi = Qmmi;
Qmmi = Qmlo;
Qmlo = 0;
bits = bits-16;
}
reqbits = bits-16;
@log_shift Qmhi bits -> sp;
@log_shift Qmmi reqbits -> sp;
@or sp sp -> Qmhi;
@log_shift Qmmi bits -> sp;
@log_shift Qmlo reqbits -> sp;
@or sp sp -> Qmmi;
@log_shift Qmlo bits -> Qmlo;
! print " ", (hex) Qmhi, (hex) Qmmi, (hex) Qmlo; new_line;
! font on;
if (c|OP1mhi|OP1mlo)
Qmlo = Qmlo | 1;
if (Qmhi>=0)
{
Qmhi = Qmhi + Qmhi; if (Qmmi<0) ++Qmhi;
Qmmi = Qmmi + Qmmi; if (Qmlo<0) ++Qmmi;
Qmlo = Qmlo + Qmlo;
--RNDexp;
}
_RoundResult(RNDsgn, RNDexp, Qmhi, Qmmi, Qmlo);
];
[ frem dest OP1 OP2;
_dyadic(FE_OP_REM, _frem, FE_FMT_S, dest, OP1, OP2);
];
[ fremx dest OP1 OP2;
_dyadic(FE_OP_REM, _frem, FE_FMT_X, dest, OP1, OP2);
];
[ _frem OP1sex OP1mhi OP1mlo OP2sex OP2mhi OP2mlo
OP1exp OP2exp RNDexp RNDsgn tmp tmp2 c iter REMhi;
!print "_frem(", (hex)OP1sex, "|", (hex)OP1mhi, (hex)OP1mlo, ",";
!print (hex)OP2sex, "|", (hex)OP2mhi, (hex)OP2mlo, ")^";
OP1exp = OP1sex & $07FF;
OP2exp = OP2sex & $07FF;
! If first operand is infinity, invalid operation
if (OP1exp == $7FF)
{
! Inf % X
_RaiseIVO(InvReason_InfRemX, OP1sex, OP1mhi, OP1mlo,
OP2sex, OP2mhi, OP2mlo);
return;
}
else if (OP1mhi >= 0)
{
! If first operand is 0, return that 0.
if ((OP1mhi|OP1mlo)==0 && (OP2mhi|OP2mlo)~=0)
{
_RoundResult(OP1sex & $8000);
return;
}
tmp = _Normalise(OP1exp, OP1mhi, OP1mlo);
OP1exp = tmp-->0;
OP1mhi = tmp-->1;
OP1mlo = tmp-->2;
}
! If second operand is infinity, return first number unchanged
if (OP2exp == $7FF)
{
_RoundResult(OP1sex & $8000, OP1exp, OP1mhi, OP1mlo);
return;
}
else if (OP2mhi >= 0)
{
! If second operand is 0, invalid operation
if ((OP2mhi|OP2mlo)==0)
{
! X % 0
_RaiseIVO(InvReason_XRem0, OP1sex, OP1mhi, OP1mlo,
OP2sex, OP2mhi, OP2mlo);
return;
}
tmp = _Normalise(OP2exp, OP2mhi, OP2mlo);
OP2exp = tmp-->0;
OP2mhi = tmp-->1;
OP2mlo = tmp-->2;
}
! Prospective exponent
RNDexp = OP2exp - 1;
! Number of iterations - 1
iter = OP1exp - RNDexp;
! Isolate prospective sign, keeping a copy
OP1sex = OP1sex & $8000;
RNDsgn = OP1sex;
if (iter < 0)
{
_RoundResult(RNDsgn, iter+RNDexp, OP1mhi, OP1mlo);
return;
}
REMhi = 0;
for (::)
{
tmp = OP2mlo - OP1mlo;
c = _UCmp(OP2mlo, OP1mlo) >= 0;
tmp2 = OP2mhi - OP1mhi + c - 1;
if (REMhi ~= 0 || ~~((OP2mhi < 0 && OP1mhi >= 0) ||
(OP2mhi < 0 && tmp2 >= 0) ||
(OP1mhi >= 0 && tmp2 >= 0)))
{
OP1mlo = tmp + OP2mlo;
c = _UCmp(OP1mlo, tmp) < 0;
OP1mhi = tmp2 + OP2mhi + c;
RNDsgn = RNDsgn + $8000;
}
if (--iter < 0) break;
@log_shift OP1mhi 0-15 -> REMhi;
OP1mhi = OP1mhi + OP1mhi; if (OP1mlo < 0) OP1mhi++;
OP1mlo = OP1mlo + OP1mlo;
}
if ((OP1mhi|OP1mlo)==0)
{
! If result is 0, should return original sign
RNDsgn = OP1sex;
RNDexp = 0;
}
else
{
tmp = _Normalise(RNDexp, OP1mhi, OP1mlo);
RNDexp = tmp-->0;
OP1mhi = tmp-->1;
OP1mlo = tmp-->2;
}
!print "result: ", (hex) RNDexp, "|", (hex) OP1mhi, (hex) OP1mlo; new_line;
_RoundResult(RNDsgn, RNDexp, OP1mhi, OP1mlo);
];
[ frnd dest OP1 rnd;
_dest = dest;
_precision = FE_FMT_S;
_rounding = rnd;
OP1 = _Promote(OP1);
_frnd(OP1-->0, OP1-->1, OP1-->2);
];
[ frndx dest OP1 rnd;
_dest = dest;
_precision = FE_FMT_X;
_rounding = rnd;
_frnd(OP1-->0, OP1-->1, OP1-->2);
];
[ _frnd OP1sex OP1mhi OP1mlo
RNDexp RNDsgn RNDgrs tmp c;
_op = FE_OP_RND;
RNDexp = OP1sex & $07FF;
RNDsgn = OP1sex & $8000;
if (RNDexp == $7FF)
{
if (OP1mhi|OP1mlo)
{
@test OP1mhi $4000 ?qnanorinf;
_RaiseIVO(InvReason_SigNaN, OP1sex, OP1mhi, OP1mlo);
return;
}
.qnanorinf;
_ReturnResult(OP1sex, OP1mhi, OP1mlo);
return;
}
if ((OP1mhi|OP1mlo)==0)
{
RNDexp = 0;
jump exact;
}
! tmp = position of binary point (relative to bottom of mantissa)
tmp = 1023+31-RNDexp;
if (tmp > 0)
{
! binary point lies within or above mantissa. denormalise so that
! binary point rests at the bottom, perform normal rounding, then
! renormalise
c = _Denorm(OP1mhi, OP1mlo, tmp);
OP1mhi = c-->0;
OP1mlo = c-->1;
RNDgrs = c-->2;
RNDexp = RNDexp + tmp;
tmp = _precision;
_precision = FE_FMT_X;
c = _RoundNum(RNDsgn, RNDexp, OP1mhi, OP1mlo, RNDgrs);
_precision = tmp;
OP1mhi = c-->0;
OP1mlo = c-->1;
RNDexp = c-->2;
if ((OP1mhi|OP1mlo)~=0)
{
c = _Normalise(RNDexp, OP1mhi, OP1mlo);
RNDexp = c-->0;
OP1mhi = c-->1;
OP1mlo = c-->2;
}
else
{
RNDexp = 0;
}
}
.exact;
_RoundResult(RNDsgn, RNDexp, OP1mhi, OP1mlo);
];
[ fsqrt dest OP1 rnd;
_dest = dest;
_precision = FE_FMT_S;
_rounding = rnd;
OP1 = _Promote(OP1);
_fsqrt(OP1-->0, OP1-->1, OP1-->2);
];
[ fsqrtx dest OP1 rnd;
_dest = dest;
_precision = FE_FMT_X;
_rounding = rnd;
_fsqrt(OP1-->0, OP1-->1, OP1-->2);
];
[ _fsqrt OP1sex OP1mhi OP1mlo
RNDexp tmp tmp2 c Qhi Qlo T lastbit;
_op = FE_OP_SQRT;
RNDexp = OP1sex & $07FF;
!print "fsqrt(", (hex) OP1sex, "|", (hex) OP1mhi, (hex) OP1mlo, ")^";
! Normal NaN handling
if (RNDexp == $7FF)
{
if (OP1mhi|OP1mlo)
{
@test OP1mhi $4000 ?qnanorinf;
_RaiseIVO(InvReason_SigNaN, OP1sex, OP1mhi, OP1mlo);
return;
}
! sqrt(+infinity) = +infinity
if (OP1sex >= 0)
{
.qnanorinf;
_ReturnResult(OP1sex, OP1mhi, OP1mlo);
return;
}
}
else if ((OP1mhi|OP1mlo)==0)
{
! sqrt(+-0) = +-0
_ReturnResult(OP1sex & $8000);
return;
}
! sqrt(negative) = invalid
if (OP1sex < 0)
{
_RaiseIVO(InvReason_SqrtNeg, OP1sex, OP1mhi, OP1mlo);
return;
}
if (OP1mhi >= 0)
{
tmp = _Normalise(RNDexp, OP1mhi, OP1mlo);
RNDexp = tmp-->0;
OP1mhi = tmp-->1;
OP1mlo = tmp-->2;
}
! exp+bias => (exp+2*bias)/2 = exp/2 + bias
RNDexp = RNDexp + 1023;
c = RNDexp & 1;
@log_shift RNDexp 0-1 -> RNDexp;
!font off;
!print "Sqrt: m=", (hex) OP1mhi, (hex) OP1mlo; new_line;
if (c == 0)
{
Qhi = OP1mhi - $8000;
T = $1000;
}
else
{
Qhi = OP1mhi - $4000;
T = $2000;
}
Qlo = OP1mlo;
OP1mhi = $8000;
OP1mlo = $0000;
! First iterations 0/1 to 13
do
{
!print (hex) OP1mhi, (hex) OP1mlo, " ",(hex) Qhi, (hex) Qlo, " ", (hex) T; new_line;
c = Qhi < 0;
Qhi = Qhi + Qhi; if (Qlo < 0) ++Qhi;
Qlo = Qlo + Qlo;
tmp = OP1mhi | T;
if (c || _UCmp(Qhi, tmp) >= 0)
{
Qhi = Qhi - tmp;
@log_shift T 1 -> sp;
@or OP1mhi sp -> OP1mhi;
}
@log_shift T 0-1 -> T;
}
until (T==0);
if ((Qhi | Qlo) == 0)
jump done;
if (_precision == FE_FMT_S)
lastbit = $0020;
else
lastbit = 0;
! Iterations 14 to 23 or 14-29
T = $8000;
do
{
!print (hex) OP1mhi, (hex) OP1mlo, " ", (hex) Qhi, (hex) Qlo, " 0000", (hex) T; new_line;
c = Qhi < 0;
Qhi = Qhi + Qhi; if (Qlo < 0) ++Qhi;
Qlo = Qlo + Qlo;
tmp = OP1mlo | T;
if (c || _UCmp(Qhi, OP1mhi) > 0 ||
(Qhi == OP1mhi && _UCmp(Qlo, tmp) >= 0))
{
Qhi = Qhi - OP1mhi; if (_UCmp(Qlo, tmp) < 0) --Qhi;
Qlo = Qlo - tmp;
if (T < 0)
OP1mhi = OP1mhi | 1;
else
{
@log_shift T 1 -> sp;
@or OP1mlo sp -> OP1mlo;
}
}
@log_shift T 0-1 -> T;
} until (T == lastbit);
!print (hex) OP1mhi, (hex) OP1mlo, " ", (hex) Qhi, (hex) Qlo; new_line;
! Iterations 30-31, if necessary
if ((Qhi | Qlo) ~= 0 && lastbit == 0)
{
! Manually crank out the last bit
c = Qhi < 0;
Qhi = Qhi + Qhi; if (Qlo < 0) ++Qhi;
Qlo = Qlo + Qlo;
if (c || _UCmp(Qhi, OP1mhi) > 0 ||
(Qhi == OP1mhi && _UCmp(Qlo, OP1mlo) > 0))
{
T = 1;
tmp2 = Qlo - OP1mlo - 1; c = (Qlo < 0 && OP1mlo >= 0) ||
(Qlo < 0 && tmp2 >= 0) ||
(OP1mlo >= 0 && tmp2 >= 0);
Qlo = tmp2;
Qhi = Qhi - OP1mhi + c - 1;
OP1mlo = OP1mlo | 1;
}
!print (hex) OP1mhi, (hex) OP1mlo, " ", (hex) Qhi, (hex) Qlo, T; new_line;
! And the round bit
c = Qhi < 0;
Qhi = Qhi + Qhi; if (Qlo < 0) ++Qhi;
Qlo = Qlo + Qlo + T;
if (c || _UCmp(Qhi, OP1mhi) > 0 ||
(Qhi == OP1mhi && _UCmp(Qlo, OP1mlo) > 0))
Qhi = $8001;
else
Qhi = 1;
Qlo = 0;
!print "Round/sticky = ", (hex) Qhi; new_line;
}
.done;
!font on;
_RoundResult(OP1sex & $8000, RNDexp, OP1mhi, OP1mlo, Qhi|Qlo);
];
[ hex x y;
@log_shift x 0-12 -> y; print (hexdigit) y;
@log_shift x 0-8 -> y; print (hexdigit) y;
@log_shift x 0-4 -> y; print (hexdigit) y;
print (hexdigit) x;
];
[ hexdigit x;
x = x & $F;
switch (x)
{
0 to 9: print (char) '0'+x;
10 to 15: print (char) 'A'-10+x;
}
];
[ fraw x;
print (hex) x-->0, (hex) x-->1;
];
[ frawx x;
print (hex) x-->0, (char) '|', (hex) x-->1, (hex) x-->2;
];
[ fhex x;
fhexx(_Promote(x));
];
[ fhexx x
exp mhi mlo tmp;
exp = x-->0;
mhi = x-->1;
mlo = x-->2;
if (exp < 0)
{
print (char) '-';
exp = exp - $8000;
}
exp = exp & $07FF;
if (exp == $7FF)
{
if ((mhi | mlo) == 0)
print "Infinity";
else
{
print "NaN";
if ((mhi & $4000) == 0) print (char) 'S';
mhi = mhi & $3FFF;
! Rearrange 8 extra bits of extra precision to come out at the top
@log_shift mlo 8 -> sp;
@log_shift sp 0-2 -> sp;
@log_shift mlo 0-8 -> sp;
@log_shift mhi 8 -> sp;
@or sp sp -> mlo;
@log_shift mhi 0-8 -> sp;
@or sp sp -> mhi;
print "($";
x = 0;
do
{
@log_shift mhi 0-12 -> tmp;
if (x || tmp)
{
x = 1;
print (hexdigit) tmp;
}
@log_shift mhi 4 -> sp;
@log_shift mlo 0-12 -> sp;
@or sp sp -> mhi;
@log_shift mlo 4 -> mlo;
@log_shift mhi 0-12 -> tmp;
} until ((mhi|mlo)==0);
if (~~x)
print (char) '0';
print (char) ')';
}
return;
}
if (mhi | mlo)
exp = exp - 1023 - 3;
else
exp = 0;
@log_shift mhi 0-12 -> tmp;
print (char) '$', (hexdigit) tmp;
mhi = mhi & $0FFF;
if (mhi | mlo)
{
print (char) '.';
@log_shift mhi 0-8 -> tmp;
print (hexdigit) tmp;
mhi = mhi & $00FF;
if (mhi | mlo)
{
@log_shift mhi 0-4 -> tmp;
print (hexdigit) tmp;
mhi = mhi & $000F;
if (mhi | mlo)
{
print (hexdigit) mhi;
if (mlo)
{
@log_shift mlo 0-12 -> tmp;
print (hexdigit) tmp;
mlo = mlo & $0FFF;
if (mlo)
{
@log_shift mlo 0-8 -> tmp;
print (hexdigit) tmp;
mlo = mlo & $00FF;
if (mlo)
{
@log_shift mlo 0-4 -> tmp;
print (hexdigit) tmp;
mlo = mlo & $000F;
if (mlo)
print (hexdigit) mlo;
}
}
}
}
}
}
print (char) 'P', exp;
];
[ fp x
sxm mhi mlo tmp exp i first last dp sig wantexp;
fstop(_workF0, x);
sxm = _workF0-->0;
mhi = _workF0-->1;
mlo = _workF0-->2;
!print "fp: ", (frawx) _workF0; new_line;
exp = (sxm & $0FF0);
@log_shift exp 0-4 -> exp;
! Turn 9 digits into ZSCII
for (i=8, tmp=mlo: i>=5: i--)
{
_fpscratch->i = '0' + (tmp & $F);
@log_shift tmp 0-4 -> tmp;
}
for (tmp=mhi: i>=1: i--)
{
_fpscratch->i = '0' + (tmp & $F);
@log_shift tmp 0-4 -> tmp;
}
_fpscratch->0 = '0' + (sxm & $F);
if (exp == $FF)
{
if (sxm<0) print (char) '-';
if ((mhi|mlo|(sxm & $F))==0)
print "Infinity";
else
{
print "NaN";
if ((sxm & 7) ~= 0 || mhi ~= 0 || mlo ~= 1)
{
print (char) '(';
if (((sxm & 7) | mhi)==0) switch (mlo)
{
$0000: print "SigNaN)"; return;
$0004: print "MagSubInf)"; return;
$0005: print "InfTimes0)"; return;
$0006: print "0TimesInf)"; return;
$0007: print "0Div0)"; return;
$0008: print "InfDivInf)"; return;
$0009: print "InfRemX)"; return;
$0010: print "XRem0)"; return;
$0011: print "SqrtNeg)"; return;
}
.asdec;
_fpscratch->0 = _fpscratch->0 & $F7;
for (i=0: i<8: i++)
if (_fpscratch->i ~= '0')
break;
for (: i<9: i++)
print (char) _fpscratch->i;
print (char) ')';
}
}
return;
}
! Turn exponent back into decimal
@log_shift exp 0-4 -> sp;
@mul sp 10 -> sp;
@and exp $F -> sp;
@add sp sp -> exp;
if (sxm & $4000) exp = -exp;
! Find how many significant digits we have after the decimal point
for (sig=8: sig>0: sig--)
{
if (_fpscratch->sig ~= '0') break;
}
++sig;
.reposition;
! Decide what the first and last digits we want are
switch (activefenv.printmode)
{
FE_PRINT_E:
first = 0;
dp = 1;
last = activefenv.printprecision;
wantexp = true;
FE_PRINT_F:
if (exp >= 0) first = 0; else first = exp;
dp = 1+exp;
last = activefenv.printprecision + exp;
wantexp = false;
FE_PRINT_G:
tmp = activefenv.printprecision;
if (tmp<=0) tmp=1;
if (exp < -4 || exp >= tmp)
{
first = 0;
dp = 1;
last = tmp-1;
if (last > sig-1) last = sig-1;
wantexp = true;
}
else
{
if (exp >= 0) first = 0; else first = exp;
dp = 1+exp;
last = tmp-1;
if (last > sig-1 && last > dp-1)
{
if (sig > dp)
last = sig-1;
else
last = dp-1;
}
wantexp = false;
}
}
!print "first=", first, " last=", last, " sig=", sig, " dp=", dp; new_line;
if (last < sig-1)
{
! trailing (non-zero) digits beyond last one we're printing
! we need to round again
i = _fpscratch->(last+1);
sig = last+1;
tmp = 0;
switch (fegetround())
{
FE_TONEAREST:
if (i > '5' ||
(i == '5' && sig > (last+2)) ||
(i == '5' && (_fpscratch->last & 1)))
tmp = 1;
FE_UPWARD:
tmp = 1;
FE_TOWARDZERO:
if (sxm < 0) tmp = 1;
}
if (tmp)
{
! Round up - add one, looping to do carries
for (i=last: i>=0: i--)
{
if (++(_fpscratch->i) == '9'+1)
{
_fpscratch->i = '0';
sig = i;
}
else
break;
}
if (i<0)
{
! Whoops - rounded right up
_fpscratch->0 = '1';
sig = 1;
exp++;
}
}
else
{
! Round down - just check trailing zeros again
for (i=last: i>=1: i--)
{
if (_fpscratch->i == '0')
sig = i;
else
break;
}
}
! Think again about what we're printing
jump reposition;
}
! XXX should we print -0?
if (sxm < 0) print (char) '-';
for (i=first: i<=last: i++)
{
if (i==dp)
print (char) '.';
if (i>=0 && i<sig)
print (char) _fpscratch->i;
else
print (char) '0';
}
if (wantexp)
{
print (char) 'E', exp;
}
];
Array _X_Ten --> $0402 $A000 $0000;
! Table look-up of powers of ten up to 10^45.
[ _GetPowerOfTen dest power rnd
a b c n s;
n = power;
s = 0;
! Halve n until it is in the range of the table
while (n > 13)
{
@log_shift n 0-1 -> n;
++s;
}
! Table of powers of ten - contains all exactly representable powers
switch (n)
{
0: a = $03FF; b = $8000;
1: a = $0402; b = $A000;
2: a = $0405; b = $C800;
3: a = $0408; b = $FA00;
4: a = $040C; b = $9C40;
5: a = $040F; b = $C350;
6: a = $0412; b = $F424;
7: a = $0416; b = $9896; c = $8000;
8: a = $0419; b = $BEBC; c = $2000;
9: a = $041C; b = $EE6B; c = $2800;
10: a = $0420; b = $9502; c = $F900;
11: a = $0423; b = $BA43; c = $B740;
12: a = $0426; b = $E8D4; c = $A510;
13: a = $042A; b = $9184; c = $E72A;
}
dest-->0 = a;
dest-->1 = b;
dest-->2 = c;
while (s > 0)
{
! Square result so far
fmulx(dest, dest, dest, rnd);
! Check next bit of power
--s;
@sub 0 s -> sp;
@log_shift power sp -> n;
if (n & 1)
fmulx(dest, dest, _X_Ten, rnd);
}
];
[ _fstop_naninf dst sex mhi mlo rnd
digits dhi dlo tmp tmp2;
!print "_fstop_naninf: ", (hex)mhi, (hex)mlo; new_line;
if ((mhi|mlo) ~= 0 && (mhi & $4000)==0)
{
! Signalling NaN
if (fpstatus & IVOE)
{
_workF0-->0 = sex; _workF0-->1 = mhi; _workF0-->2 = mlo;
tmp = activefenv.ivo_handler;
@call_vn2 tmp dst FE_FMT_P FE_OP_DEC rnd InvReason_SigNaN _workF0;
}
else
{
fpstatus = fpstatus | FE_INVALID;
dst-->0 = $0FF8;
dst-->1 = $0000;
dst-->2 = InvReason_SigNaN; ! BCD, but okay
}
return;
}
sex = (sex & $8000) | $0FF0;
if (mhi) sex = sex | $0008;
mhi = mhi & $3FFF;
!print "Rearranging InfNaN: ", (hex)mhi, (hex)mlo; new_line;
! Infinity/NaN
! Should trap signalling NaNs - currently caught by fstox()
!
! We do actually convert the bits of the NaN (or indeed infinity) into
! BCD. We are actually converting a single-precision NaN, and don't
! want the bottom 8 bits. So it's a conversion of 22 bits -> 7 digits.
@log_shift mlo 0-8 -> sp;
@log_shift mhi 8 -> sp;
@or sp sp -> mlo;
@log_shift mhi 0-8 -> mhi;
!print "Rearranged InfNaN: ", (hex)mhi, (hex)mlo; new_line;
! Oh gawd, don't ask. This is a binary->decimal
! conversion of (mhi,mlo) -> (dhi,dlo). Each step of
! the loop divides (mhi,mlo) by ten, by using an approximation
! 1/10 = 4/5 * 1/8 ~= $0.CCCCCCCC * 1/8
! m = $0.CCCCCCCC * i is approximated by j = i - (i>>2), k = j + (j>>4),
! l = k + (k>>8), m = l + (l>>16)
! This approvidation gives i DIV 10 <= (m>>3) <= i DIV 10 + 15, and
! we just check the remainder at the end.
for (digits=0: digits<7: digits++)
{
tmp2 = mlo;
!print "Digit ", digits; new_line;
if (mhi ~= 0 || mlo < 0)
{
! print "i=", (hex) mhi, (hex) mlo; new_line;
@log_shift mlo 0-2 -> sp;
@log_shift mhi 14 -> sp;
@or sp sp -> tmp;
@log_shift mhi 0-2 -> sp;
@sub mhi sp -> mhi;
if (_UCmp(mlo, tmp) < 0) --mhi;
mlo = mlo - tmp;
! print "j=", (hex) mhi, (hex) mlo; new_line;
@log_shift mlo 0-4 -> sp;
@log_shift mhi 12 -> sp;
@or sp sp -> tmp;
mlo = mlo + tmp;
@log_shift mhi 0-4 -> sp;
@add mhi sp -> mhi;
if (_UCmp(mlo, tmp) < 0) ++mhi;
! print "k=", (hex) mhi, (hex) mlo; new_line;
@log_shift mlo 0-8 -> sp;
@log_shift mhi 8 -> sp;
@or sp sp -> tmp;
mlo = mlo + tmp;
@log_shift mhi 0-8 -> sp;
@add mhi sp -> mhi;
if (_UCmp(mlo, tmp) < 0) ++mhi;
! print "l=", (hex) mhi, (hex) mlo; new_line;
mlo = mlo + mhi;
if (_UCmp(mlo, mhi) < 0) ++mhi;
! print "m=", (hex) mhi, (hex) mlo; new_line;
@log_shift mlo 0-3 -> mlo;
@log_shift mhi 13 -> sp;
@or mlo sp -> mlo;
@log_shift mhi 0-3 -> mhi;
! print "m>>3=", (hex) mhi, (hex) mlo; new_line;
@log_shift mlo 2 -> sp;
@add mlo sp -> tmp;
@log_shift tmp 1 -> tmp;
tmp = tmp2 - tmp;
! print "remainder=", tmp; new_line;
if (tmp >= 10)
{
tmp = tmp - 10;
if (++mlo==0) ++mhi;
}
}
else
{
tmp = mlo % 10;
mlo = mlo / 10;
}
@log_shift dlo 0-4 -> dlo;
@log_shift dhi 12 -> sp;
@or dlo sp -> dlo;
@log_shift dhi 0-4 -> dhi;
@log_shift tmp 8 -> sp;
@or dhi sp -> dhi;
! print "squirreled=", (hex)dhi, (hex)dlo; new_line;
}
!print"Converted decimal = ", (hex) dhi, (hex) dlo; new_line;
dst-->0 = sex;
dst-->1 = dhi;
dst-->2 = dlo;
];
! This is hard
! Binary -> decimal conversion. We only provide single-precision conversions,
! as required by the standard, using extended precision to make it work.
!
! The destination format is BCD: $SEEM $MMMM $MMMM representing
! <+/->M.MMMMMMMM x 10^(<+/->EE), M and E being BCD, top bit of S being the
! sign of the number, next bit of S being the sign of the exponent.
[ fstop dst tmp rnd ! tmp = src
sex mhi mlo exp arith tmp2 tmp3 tmp4
inexact digits grs c;
fstox(_workF0, tmp, 1);
sex = _workF0-->0;
mhi = _workF0-->1;
mlo = _workF0-->2;
exp = sex & $07FF;
if (rnd==0) rnd = activefenv.rounding;
!print "fstop(", (hex) sex, "|", (hex) mhi, (hex) mlo, ")^";
if (exp == $7FF)
{
_fstop_naninf(dst, sex, mhi, mlo, rnd);
return;
}
if ((mhi | mlo) == 0)
{
sex = sex & $8000;
jump done;
}
! Now have a normalised (originally single precision) number, in
! extended form. exp is in the range 1-23+(1023-127) to +254+(1023-127)
! = $36A to $47E. We now add one to the exponent (so the mantissa
! lies within [1/2 .. 1), and remove the bias.
arith = exp - 1023 + 1;
! arith is now in the range [-148 .. +128]. We need to
! make it a decimal exponent. This needs a logarithm, but we'll start off
! with an approximation that can only be off by +1.
!
! We know:
!
! 2^(arith-1) <= value < 2^arith
!
! Taking base-10 logarithms:
!
! (arith-1)*log(2) <= log(value) < arith*log(2)
!
! Let log2lo and log2hi be slightly too low and high approximations to log(2).
!
! if (arith > 0): (arith-1)*log2lo <= log(value) < arith*log2hi
! if (arith <= 0): (arith-1)*log2hi <= log(value) < arith*log2lo
!
! Let D = log2hi-log2lo:
!
! if (arith > 0)
! arith*log2hi - arith*D - log2lo <= log(value) < arith*log2hi
! if (arith <= 0)
! arith*log2lo - (-arith*D) - log2hi <= log(value) < arith*log2lo
!
! Then, provided that log2lo and log2hi are such that (128*D+log2lo) <= 1
! and (148*D+log2hi) <= 1:
!
! if (arith > 0)
! floor(arith*log2hi) - 1 <= floor(log(value)) <= floor(arith*log2hi)
! if (arith <= 0)
! floor(arith*log2lo) - 1 <= floor(log(value)) <= floor(arith*log2lo)
!
! Which gives us the desired bounds.
!
! The conditions are satisfied as long as D <= 2^(-8), but we want as
! much accuracy as we can get without overflowing a 16-bit multiplication.
! We can afford to set D to 2^(-9) giving us 8 bits of accuracy in log2,
! and 7 bits of accuracy in arith, leading to a 15-bit result.
!
! So we choose log2lo = 154 * 2^(-9) = ~ 0.30078 (log(2) = ~0.30103)
! log2hi = 155 * 2^(-9) = ~ 0.30273
if (arith > 0)
tmp2 = 155; ! 2^9 * log2hi
else
tmp2 = 154; ! 2^9 * log2lo
tmp2 = arith * tmp2;
@art_shift tmp2 0-9 -> arith;
!print "approximate exponent=", arith; new_line;
! Now arith-1 <= floor(log(value)) = base-10 exponent <= arith
if (arith >= 0)
{
tmp = arith;
if (arith == 0)
jump expadjustdone;
}
else
tmp = -arith;
! We now need to multiply the original value by 10^(-arith) to get
! the correct decimal mantissa.
! We'll use some FP - remember status, and disable exceptions
tmp2 = fpstatus;
fpstatus = 0;
_GetPowerOfTen(_workF1, tmp, FE_TONEAREST);
if (arith >= 0)
fdivx(_workF0, _workF0, _workF1, FE_TONEAREST);
else
fmulx(_workF0, _workF0, _workF1, FE_TONEAREST);
! Check inexact (either in 10^tmp, or multiplication/division)
inexact = fpstatus & FE_INEXACT;
fpstatus = tmp2;
!print "After exponent extraction: ", (frawx) _workF0; new_line;
! Get the value back
sex = _workF0-->0;
mhi = _workF0-->1;
mlo = _workF0-->2;
.expadjustdone;
exp = sex & $07FF;
sex = sex & $8000;
digits = 9;
! Shift the mantissa so the binary point is between bits 12 and 11 of mhi
! The extra bits (not many) go into grs.
exp = 1023 + 3 - exp;
tmp = 16 - exp;
tmp2 = -exp;
@log_shift mlo tmp -> grs;
@log_shift mlo tmp2 -> mlo;
@log_shift mhi tmp -> sp;
@or mlo sp -> mlo;
@log_shift mhi tmp2 -> mhi;
!print "Shifted mantissa: ", (hex) mhi, (hex) mlo, (hex) grs; new_line;
! If the mantissa is <1, decrement the arith exponent, and proceed
! to "multiply by ten", otherwise extract the first digit.
exp = 0;
tmp2 = 0;
if (mhi & $F000)
jump extract_digit;
--arith;
! Stage one - three words to go, accumulating into two words
do
{
! First multiply by 2
mhi = mhi + mhi; if (mlo < 0) ++mhi;
mlo = mlo + mlo; if (grs < 0) ++mlo;
grs = grs + grs;
! Then by five - work out (mhi,mlo,grs)*4 + (mhi,mlo,grs)
@log_shift grs 2 -> tmp3;
@log_shift grs 0-14 -> sp;
@log_shift mlo 2 -> sp;
@or sp sp -> tmp4;
@log_shift mlo 0-14 -> sp;
@log_shift mhi 2 -> sp;
@or sp sp -> tmp;
grs = grs + tmp3;
c = _UCmp(grs, tmp3) < 0;
tmp3 = mlo + tmp4 + c;
c = (mlo < 0 && tmp4 < 0) ||
(mlo < 0 && tmp3 >= 0) ||
(tmp4 < 0 && tmp3 >= 0);
mlo = tmp3;
mhi = mhi + tmp + c;
! print "Times 10: ", (hex) mhi, (hex) mlo, (hex) grs; new_line;
.extract_digit;
! The integer part of the number is the next digit. Move it up into
! exp, and decrement the digit count.
@log_shift tmp2 4 -> sp;
@log_shift exp 0-12 -> sp;
@or sp sp -> tmp2;
@log_shift exp 4 -> sp;
@log_shift mhi 0-12 -> sp;
@or sp sp -> exp;
mhi = mhi & $0FFF;
--digits;
} until (grs==0);
tmp = 0;
! Second loop - two words to process in (mhi,mlo) - accumulating
! into (tmp,tmp2,exp).
do
{
! Multiply by 2 then 5, as before
mhi = mhi + mhi; if (mlo < 0) ++mhi;
mlo = mlo + mlo;
@log_shift mlo 0-14 -> sp;
@log_shift mlo 2 -> tmp3;
mlo = mlo + tmp3;
c = _UCmp(mlo, tmp3) < 0;
@log_shift mhi 2 -> sp;
@or sp sp -> tmp3;
mhi = mhi + tmp3 + c;
!print "Times 10: ", (hex) mhi, (hex) mlo; new_line;
! Extract the digit
@log_shift tmp 4 -> sp;
@log_shift tmp2 0-12 -> sp;
@or sp sp -> tmp;
@log_shift tmp2 4 -> sp;
@log_shift exp 0-12 -> sp;
@or sp sp -> tmp2;
@log_shift exp 4 -> sp;
@log_shift mhi 0-12 -> sp;
@or sp sp -> exp;
mhi = mhi & $0FFF;
--digits;
} until (digits==0);
!print "Inexact=", inexact, " mhi|mlo=", (hex)mhi, (hex)mlo; new_line;
! Get final inexactitude. In practice, this doesn't work very well,
! as there are many exact cases where the two errors cancel each other
! out.
inexact = inexact | mhi | mlo;
@log_shift mhi 0-11 -> c; ! Round bit
mhi = (mhi & $07FF) | mlo; ! Sticky bits
!if (inexact || c || mhi)
!{
! if (inexact) print "Inexact ";
! if (c) print "Round ";
! if (mhi) print "Sticky ";
! new_line;
!}
mlo = 0; ! round up flag
switch (rnd)
{
FE_TONEAREST:
if (c)
if (mhi || (exp & 1))
mlo = 1;
FE_UPWARD:
if (sex >= 0 && (c | mhi))
mlo = 1;
FE_DOWNWARD:
if (sex < 0 && (c | mhi))
mlo = 1;
}
if (mlo)
{
! print "BCD++: ", (hex) tmp, (hex) tmp2, (hex) exp;
++exp;
if ((exp & $F) == 10)
{
! Need to start do a BCD carry
@log_shift exp 0-1 -> c;
tmp3 = c + $3333;
tmp3 = (tmp3 &~ c) | (c &~ tmp3);
tmp3 = tmp3 & $8888;
@log_shift tmp3 0-2 -> sp;
@mul sp 3 -> sp;
@add exp sp -> exp;
if (tmp3 & $8000)
{
tmp2 = tmp2 + 1;
@log_shift tmp2 0-1 -> c;
tmp3 = $3333 + c;
tmp3 = (tmp3 &~ c) | (c &~ tmp3);
tmp3 = tmp3 & $8888;
@log_shift tmp3 0-2 -> sp;
@mul sp 3 -> sp;
@add tmp2 sp -> tmp2;
if (tmp3 & $8000)
{
tmp = tmp + 1;
@log_shift tmp 0-1 -> c;
tmp3 = $3333 + c;
tmp3 = (tmp3 &~ c) | (c &~ tmp3);
tmp3 = tmp3 & $8888;
@log_shift tmp3 0-2 -> sp;
@mul sp 3 -> sp;
@add tmp sp -> tmp;
if (tmp & $0010)
{
tmp = 1;
++arith;
}
}
}
}
! print " -> ", (hex) tmp, (hex) tmp2, (hex) exp;
}
sex = sex | tmp;
mhi = tmp2;
mlo = exp;
if (arith < 0)
{
sex = sex | $4000;
arith = -arith;
}
tmp = 0;
if (arith >= 80) { arith = arith - 80; tmp = tmp + $80; }
if (arith >= 40) { arith = arith - 40; tmp = tmp + $40; }
if (arith >= 20) { arith = arith - 20; tmp = tmp + $20; }
if (arith >= 10) { arith = arith - 10 + $10; }
tmp = tmp + arith;
@log_shift tmp 4 -> tmp;
sex = sex | tmp;
! print "^Final result: ", (hex) sex, (hex) mhi, (hex) mlo; new_line;
.done;
dst-->0 = sex;
dst-->1 = mhi;
dst-->2 = mlo;
if (inexact)
{
if (fpstatus & INXE)
activefenv.inx_handler(dst, FE_FMT_P, FE_OP_DEC, fegetround());
else
fpstatus = fpstatus | FE_INEXACT;
}
];
! Accumulate n BCD digits from the top of src into (dest-->0,dest-->1)
[ _readdigits dest src n
hi lo tmp c;
hi = dest-->0;
lo = dest-->1;
do
{
! First multiply by 10
hi = hi + hi; if (lo < 0) ++hi;
lo = lo + lo;
@log_shift lo 0-14 -> sp;
@log_shift lo 2 -> tmp;
lo = lo + tmp;
c = _UCmp(lo, tmp) < 0;
@log_shift hi 2 -> sp;
@or sp sp -> tmp;
hi = hi + tmp + c;
! Then add in the new digit
@log_shift src 0-12 -> tmp;
lo = lo + tmp;
if (_UCmp(lo, tmp) < 0) ++hi;
@log_shift src 4 -> src;
}
until (--n == 0);
dest-->0 = hi;
dest-->1 = lo;
];
[ _fptos_naninf dest sex mhi mlo rnd;
!print "_fptos_naninf(", (hex) sex, (hex) mhi, (hex) mlo; print ")^";
if ((mhi | mlo | (sex & $F)) == 0)
{
! Infinity
sex = (sex & $8000) | $7F80;
jump done;
}
! Check quiet / signalling NaN
! (we don't trigger signalling NaNs until converted, as we're
! supposed to supply the trap handler in extended form. Is this
! sensible?)
@test sex $0008 ?~sig;
@or sex $0040 -> sex;
.sig;
sex = (sex & $8040) | $7F80;
! Pull out the bottom 7 digits only - all we care about for NaNs
_fpscratch-->0 = 0;
_fpscratch-->1 = 0;
@log_shift mhi 4 -> mhi;
_readdigits(_fpscratch, mhi, 3);
_readdigits(_fpscratch, mlo, 4);
! (mhi,mlo) = value for NaN. Could be 24 bits if unusual - knock back to
! 22, and then we're all ready
mhi = _fpscratch-->0;
mlo = _fpscratch-->1;
!print "Read digits: ", (hex) mhi, (hex) mlo; new_line;
mhi = mhi & $003F;
sex = sex | mhi;
if ((sex & $0040)==0)
{
! We now trigger the NaN.
_dest = dest;
_precision = FE_FMT_S;
_op = FE_OP_DEC;
_rounding = rnd;
_RaiseIVO(InvReason_SigNaN, sex, mhi, mlo);
return;
}
.done;
dest-->0 = sex;
dest-->1 = mlo;
];
[ fptos dest src rnd
sex mhi mmi mlo tmp arith;
sex = src-->0;
mmi = src-->1;
mlo = src-->2;
@log_shift sex 4 -> tmp;
_fpscratch-->0 = 0;
_fpscratch-->1 = 0;
_readdigits(_fpscratch, tmp, 2);
!print "Exponent = ", _fpscratch-->1; new_line;
arith = _fpscratch-->1;
if (arith > 99)
{
_fptos_naninf(dest, sex, mmi, mlo, rnd);
return;
}
_fpscratch-->0 = 0;
_fpscratch-->1 = sex & $F;
_readdigits(_fpscratch, mmi, 4);
_readdigits(_fpscratch, mlo, 4);
!print "Full mantissa = ", (hex) _fpscratch-->0, (hex) _fpscratch-->1; new_line;
mhi = _fpscratch-->0;
mlo = _fpscratch-->1;
! Short cut for zero
if ((mhi|mlo)==0)
{
dest-->0 = sex & $8000;
dest-->1 = 0;
return;
}
if (sex & $4000)
arith = -arith;
! Adjust because we've got a 9 digit integer, not 1.8 digit decimal
arith = arith - 8;
! Want to convert (mhi,mlo) into an extended precision number
! Need to normalise. We know (mhi,mlo)< 10^9 < 2^30
sex = sex & $8000;
sex = sex + 1023 + 31;
while (mhi >= 0)
{
mhi = mhi + mhi; if (mlo < 0) ++mhi;
mlo = mlo + mlo;
--sex;
}
!print (hex) sex, (char) '|', (hex) mhi, (hex) mlo; new_line;
_workF0-->0 = sex;
_workF0-->1 = mhi;
_workF0-->2 = mlo;
! Now just need to multiply by 10^arith. |arith| <= 99, so overflow
! isn't possible (we're spared because we refuse to do binary<->decimal
! on extended precision).
tmp = fpstatus;
fpstatus = 0;
if (arith > 0)
{
_GetPowerOfTen(_workF1, arith, FE_TONEAREST);
fmulx(_workF0, _workF0, _workF1, FE_TONEAREST);
}
else if (arith < 0)
{
_GetPowerOfTen(_workF1, -arith, FE_TONEAREST);
fdivx(_workF0, _workF0, _workF1, FE_TONEAREST);
}
if (fpstatus & FE_INEXACT)
arith = $8000;
else
arith = 0;
fpstatus = tmp;
! print "Extended result = ", (frawx) _workF0; new_line;
! Round back to single
_precision = FE_FMT_S;
_dest = dest;
_rounding = rnd;
_op = FE_OP_DEC;
_RoundResult(_workF0-->0 & $8000, _workF0-->0 & $07FF,
_workF0-->1, _workF0-->2, 0, arith);
!print "Final result = ", (fraw) dest; new_line;
];
[ _strtof_inf dest str cnt len sign;
--cnt;
str = str + cnt;
dest-->1 = $0000;
if (len >= 3 &&
(str->1 & $DF)=='N' &&
(str->2 & $DF)=='F')
{
dest-->0 = sign | $7F80;
if (len >= 8 &&
(str->3 & $DF)=='I' &&
(str->4 & $DF)=='N' &&
(str->5 & $DF)=='I' &&
(str->6 & $DF)=='T' &&
(str->7 & $DF)=='Y')
return cnt+8;
else
return cnt+3;
}
dest-->0 = $0000;
return 0;
];
[ _strtof_nan dest str cnt len sign;
--cnt;
str = str + cnt;
if (len >= 3 &&
(str->1 & $DF)=='A' &&
(str->2 & $DF)=='N')
{
dest-->1 = InvReason_InitNan;
if (len >= 4 &&
(str->3 & $DF)=='S')
{
dest-->0 = sign | $7F80;
return cnt+4;
}
else
{
dest-->0 = sign | $7FC0;
return cnt+3;
}
}
dest-->0 = $0000;
dest-->1 = $0000;
return 0;
];
! Convert ZSCII string to single. len is length of string (will
! not read beyond this). Returns number of characters consumed.
[ strtof dest str len
c hex sign had_dot num_ok dhi dmi dlo exp cnt exp2 negexp;
! print "strtof($", (hex) dest, ",$", (hex) str, ",", len, ")^";
if (len>=0) c = str->(cnt++);
while (len>0 && c==' ')
if (--len>0) c = str->(cnt++);
if (len>0 && (c=='+' || c=='-'))
{
if (c=='-') sign = $8000;
if (--len>0) c = str->(cnt++);
}
if (len>0) switch (c)
{
'$': hex = 1; if (--len>0) c = str->(cnt++);
'n','N': return _strtof_nan(dest, str, cnt, len, sign);
'i','I': return _strtof_inf(dest, str, cnt, len, sign);
}
while (len > 0)
{
!print "Got '", (char) c, "'^";
if (c=='.' && ~~had_dot)
had_dot=true;
else if ((c>='0' && c<='9') ||
(hex && ((c>='a' && c<='f') || (c>='A' && c<='F'))))
{
num_ok=true;
if (c<='9') c=c-'0';
else if (c<='F') c=c-'A'+10;
else c=c-'a'+10;
if (dhi==0)
{
@log_shift dmi 0-12 -> dhi;
@log_shift dmi 4 -> sp;
@log_shift dlo 0-12 -> sp;
@or sp sp -> dmi;
@log_shift dlo 4 -> sp;
@or sp c -> dlo;
if (had_dot) --exp;
}
else
{
if (hex && c ~= '0') dlo = dlo | 1;
if (~~had_dot) ++exp;
}
}
else
break;
if (--len>0) c = str->(cnt++);
}
if (hex) exp = exp * 4;
if (len>0 && num_ok &&
(c=='e' || c=='E' || (hex && (c=='p' || c=='P'))))
{
num_ok=false;
if (--len>0)
{
c = str->(cnt++);
if (c=='-' || c=='+')
{
if (c=='-') negexp = true;
if (--len>0) c = str->(cnt++);
}
while (len>0 && c>='0' && c<='9')
{
num_ok=true;
exp2 = exp2*10 + c-'0';
if (--len>0) c = str->(cnt++);
}
if (negexp)
exp = exp-exp2;
else
exp = exp+exp2;
}
}
if (len>0) cnt--;
if (~~num_ok)
{
dest-->0=$0000;
dest-->1=$0000;
return 0;
}
!print (hex) dhi, (hex) dmi, (hex) dlo, " E", exp; new_line;
if ((dhi|dmi|dlo)==0)
{
dest-->0=sign; ! Do we want signed zero input? Why not?
dest-->1=$0000;
}
else
{
if ((dhi|dmi)==0)
{
dmi = dlo;
dlo = 0;
if (hex) exp=exp-16; else exp=exp-4;
}
! print (hex) dhi, (hex) dmi, (hex) dlo, " E", exp; new_line;
if (hex)
{
exp=exp+31+16+1023;
while (dhi == 0)
{
dhi = dmi;
dmi = dlo;
dlo = 0;
exp = exp - 16;
}
while (dhi >= 0)
{
dhi = dhi+dhi; if (dmi < 0) dhi++;
dmi = dmi+dmi; if (dlo < 0) dmi++;
dlo = dlo+dlo;
exp--;
}
! print (hex) dhi, (hex) dmi, (hex) dlo, " P", exp; new_line;
_precision = FE_FMT_S;
_dest = dest;
_rounding = 0; ! FE_TONEAREST?
_op = FE_OP_DEC;
_RoundResult(sign, exp, dhi, dmi, dlo);
}
else
{
while (dhi==0)
{
@log_shift dmi 0-12 -> dhi;
@log_shift dmi 4 -> sp;
@log_shift dlo 0-12 -> sp;
@or sp sp -> dmi;
@log_shift dlo 4 -> dlo;
exp--;
}
exp = exp+8;
if (exp < -99 || exp > 99)
{
! raise overflow exception (or underflow)
dest-->0 = sign | $7F80;
dest-->1 = 0;
return cnt;
}
else
{
if (exp < 0) { exp = -exp; dhi = dhi | $4000; }
dhi = dhi | sign;
@div exp 10 -> sp;
@log_shift sp 8 -> sp;
@or dhi sp -> dhi;
@mod exp 10 -> sp;
@log_shift sp 4 -> sp;
@or dhi sp -> dhi;
_workF0-->0=dhi;
_workF0-->1=dmi;
_workF0-->2=dlo;
! print (frawx) _workF0; new_line;
fptos(dest, _workF0);
}
}
}
return cnt;
];
#Ifndef StorageForShortName;
Array StorageForShortName --> 161;
#Endif;
! Initialise dest from a packed string
[ finit dest str
len n;
@output_stream 3 StorageForShortName;
print (string) str;
@output_stream $FFFD;
len = StorageForShortName-->0;
if (len > 320)
{
print "[** Overlong floating-point constant **]^"; quit;
}
n = strtof(dest, StorageForShortName + 2, len);
if (n ~= len)
print "[** Floating-point constant ~", (string) str, "~ not recognized **]^";
];

Xet Storage Details

Size:
81.4 kB
·
Xet hash:
a292bf4da88bdc89fa12cd271fc5c2c71abfd9cac7c8c938ebd3861147c9006d

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.