| ! ---------------------------------------------------------------------------- | |
| ! 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.