! ----------------------------------------------------------------------------
!  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 **]^";
];