;-------------------------------------------------------------
; ASM source for 1024-byte command-line calculator.  Requires
; 80387 (e.g., 486DX or Pentium).  Intended as 80387 program
; demo.  Text width here convenient for 2-page HPTINY print.
;
; Syntax: CALC387 math_expression         Calculates result
;         CALC387                         Shows usage
;
; 1+4*2^3-5/4%1 --> 32.75                 Usual precedence
; ((1+4)*2)^3       1000                  Can override
; -MEM^(1/3)       -10                    MEM is last result
; 1 / 7             0.14286               Default 5 decimals
; 1.0000000/7       0.1428571             Extra implied
; 1E0/7             1.428571428571429E-1  Max if E-notation
; E*COS(PI)        -2.71828               Constants e and pi
; 1/0               ?                     Error indicator
;
; LOG/LN/LG/SIN/COS/TAN/INT/SIGN/RND/ASIN/ACOS/ATAN/SQR/ABS
;
; If no 80387 is detected, question mark error indicator is
; always displayed, no matter what the expression is.
;
; Program has four sections:  top level calling sequence,
; recursive expression parser, 25 short FPU jump table
; routines, and two long FPU I/O routines--ASCII number to
; ST and vice versa.  Total code/data is 0.8K/0.2K.
;
; craig.hessel@mailgw.er.doe.gov
;-------------------------------------------------------------
; FTST map for flags C3/C2/C1/C0 (equality/parity/?/carry):
;
; 0/0/?/0 = ST positive   1/0/?/0 = ST zero
; 0/0/?/1 = ST negative   1/1/?/1 = ST is NAN
;
; FCOM (and variations) map for flags C3/C2/C1/C0:
;
; 0/0/?/0 = ST > source   1/0/?/0 = ST equals source
; 0/0/?/1 = ST < source   1/1/?/1 = Not comparable
;
; FXAM map for flags C3/C2/C1/C0:
;
; 0/0/0/0 =  Unused   0/1/0/0 = +Normal
; 0/0/0/1 = +NAN      0/1/0/1 = +Infinity
; 0/0/1/0 =  Unused   0/1/1/0 = -Normal
; 0/0/1/1 = -NAN      0/1/1/1 = -Infinity
;
; 1/0/0/0 = +Zero     1/1/0/0 = +Denormal
; 1/0/0/1 =  Empty    1/1/0/1 =  Unused
; 1/0/1/0 = -Zero     1/1/1/0 = -Denormal
; 1/0/1/1 =  Empty    1/1/1/1 =  Unused
;
; Some "unused" had meaning for pre-80387 FPUs.
;-------------------------------------------------------------
MOVE       EQU    xchg             ; Saves byte on AX moves
FIXMAX     EQU    19               ; Caps fixed display
FIXDFLT    EQU    5                ; Default output decimals
TAILZERO   EQU    (2*FIXMAX-16)    ; Tail zeros in FltToASC
NUMCON     EQU    3                ; Number constant symbols
NUMUNRY    EQU    16               ; Number unary op symbols
NUMBNRY    EQU    6                ; Number binary ops

POSDIFF    =      OFFSET PosNum  - OFFSET RndNum
NEGDIFF    =      OFFSET NegNum  - OFFSET RndNum
ABSDIFF    =      OFFSET AbsNum  - OFFSET RndNum
SQRDIFF    =      OFFSET SqrNum  - OFFSET RndNum
ATANDIFF   =      OFFSET ATanNum - OFFSET RndNum
ACOSDIFF   =      OFFSET ACosNum - OFFSET RndNum
ASINDIFF   =      OFFSET ASinNum - OFFSET RndNum
RNDDIFF    =      OFFSET RndNum  - OFFSET RndNum
SIGNDIFF   =      OFFSET SignNum - OFFSET RndNum
INTDIFF    =      OFFSET IntNum  - OFFSET RndNum
TANDIFF    =      OFFSET TanNum  - OFFSET RndNum
COSDIFF    =      OFFSET CosNum  - OFFSET RndNum
SINDIFF    =      OFFSET SinNum  - OFFSET RndNum
LGDIFF     =      OFFSET LgNum   - OFFSET RndNum
LNDIFF     =      OFFSET LnNum   - OFFSET RndNum
LOGDIFF    =      OFFSET LogNum  - OFFSET RndNum
MEMDIFF    =      OFFSET MemNum  - OFFSET RndNum
PIDIFF     =      OFFSET PiNum   - OFFSET RndNum
EDIFF      =      OFFSET ENum    - OFFSET RndNum
EXPDIFF    =      OFFSET ExpNum  - OFFSET RndNum
MODDIFF    =      OFFSET ModNum  - OFFSET RndNum
DIVDIFF    =      OFFSET DivNum  - OFFSET RndNum
MULDIFF    =      OFFSET MulNum  - OFFSET RndNum
SUBDIFF    =      OFFSET SubNum  - OFFSET RndNum
ADDDIFF    =      OFFSET AddNum  - OFFSET RndNum

Code_Seg   SEGMENT
           ASSUME CS:Code_Seg,DS:Code_Seg,ES:Code_Seg

           .386                    ; Actually only 286 and
           .387                    ;   387 instructions used

           ORG    50h
Int21hPSP  LABEL  PROC             ; For final display/exit
           ORG    100h             ; COM start
;-------------------------------------------------------------
; Top level sequence pre-processes expression, tests for 387,
; evaluates expression, checks for calculation error, then
; displays result and exits.  Early aborts possible.
;
; CLD from DOS and zero CH are program defaults assumed often.
; AH is zero from DOS unless bad second drive specifier, in
; which case bad expression anyway, so first CWD ok.
;-------------------------------------------------------------
Calc387:   pop    cx               ; Counts bytes--DOS zero
           push   cs               ; For final far return
           push   cx               ;   to PSP start
           cwd                     ; Counts parentheses
           mov    si,81h           ; Command-line start
           mov    di,si            ; Copy in place
           push   di               ; Preserve for Evaluate
           dec    di               ; Dummies first STOSB
;-------------------------------------------------------------
; Force upper case, strip spaces, check parenthesis balance,
; make digits binary and add extra ')'.  Symbols reduced by 16
; as side effect:  ().+-*/% (not ^).  Compares include -16.
;-------------------------------------------------------------
bumpbal:   inc    dx               ; Bump for left parenthesis
chkloop:   inc    cx               ; Count bytes aside from CR
           stosb                   ; Store adjusted byte
prechk:    lodsb                   ; Fetch next byte
           cmp    al,13
           je     chkout           ; End command-line?  Exit

           and    al,NOT 20h       ; To upper for alpha or ^
           je     prechk           ; Ignore spaces

           cmp    al,'A'
           jnb    chkloop          ; Letter or higher?  Loop

           xor    al,10h           ; Digits to binary
           cmp    al,'('-16        ; Test left parenthesis
           je     bumpbal          ; Yes?  Bump count/store

           cmp    al,')'-16        ; Test right parenthesis
           jne    chkloop          ; No?  Loop

           dec    dx               ; Reduce balance count
           jne    chkloop          ; Ok?  Loop, else exit

chkout:    pop    si               ; Restore start offset
           dec    dx               ; Offset initial bump
           jne    abort            ; Unbalanced?  Abort

           mov    dx,OFFSET Syntax
           dec    cx               ; Offset initial bump
           je     showmsg          ; No input?  Display syntax

           mov    al,')'-16        ; Extra ')' terminates
           stosb                   ;   later recursion
           mov    di,dx            ; Will overwrite syntax
;-------------------------------------------------------------
; Test for 80387.  Needed for FSIN/FCOS and larger operand
; ranges.  FNSAVE used instead of FNINIT to get Mem value (old
; ST) following 14 control bytes.  If prior state unneeded,
; FNINIT may be substituted.  Test hinges on PUSH SP and FINIT
; quirks.  80386 assured if 80387, but 80386/287 possible.
;-------------------------------------------------------------
           push   sp               ; On 8086/88, post-push SP
           pop    ax               ;   is stored
           sub    ax,sp            ; 2 if 8086/88, else 0
           jne    abort            ; 8086/88?  Then no 387 FPU

           fnsave [di]             ; Also initializes FPU
           dec    ax               ; To 0FFFFh
           fnstsw ax               ; Status low byte is zero
           inc    ax               ; To zero if was no change
           je     abort            ; No change?  Then no FPU

           fldz                    ; Got FPU--now test 287
           ftst                    ; Force FPU equality
           fninit                  ; Clears codes if 387 but
           fnstsw ax               ;   leaves intact if 287
           sahf                    ; C3 code to CPU flag
           je     abort            ; Intact?  Then not 387
;-------------------------------------------------------------
; Got 80387, so can evaluate expression.  Initial BP adjusted
; in ASCToFlt.  Test afterward catches all junk--bad Mem,
; overflow, range error, etc.  Final display/exit is via PSP
; only so program contains no explicit INTs.
;-------------------------------------------------------------
           mov    bp,FIXDFLT       ; Default fixed decimals
           call   Evaluate         ; May abort internally
           fxam                    ; Examine final ST
           fnstsw ax               ; See condition code map
           sahf
           jc     calcabort        ; C0 set?  Abort
           jnp    ok               ; C2 clear?  Ahead
           je     calcabort        ; C3 set?  Abort

ok:        push   di               ; Points into high data
           fld    st               ; Extra ST copy for exit
           call   FltToASC         ; ST to ASCII at DI
           pop    dx
stoterm:   mov    ax,0900h+'$'     ; High byte is DOS function
           stosb                   ; DOS string terminator
           jmp    Int21hPSP        ; Display string and exit
;-------------------------------------------------------------
; Abort/message handling.  Context left on stack if evalabort,
; since quitting.  For other use, initial SP should be saved
; insided Evaluate then restored on abort, with error flag
; returned.  DI safe for unneeded '$' store afterward.
;-------------------------------------------------------------
evalabort: push   cs               ; Ready far return exit
           push   cx               ; CX zero from below
calcabort: call   MemNum           ; Mem to ST on calc error
abort:     mov    dx,OFFSET QMark  ; Question mark on error
showmsg:   jmp    SHORT stoterm    ; Display and exit
;-------------------------------------------------------------
; Get and compare precedence levels 0-3.  Also entry here for
; binary op test, which zeroes CX if test fails.
;-------------------------------------------------------------
GetPrec:   call   GetOne           ; Get AH level first
GetOne:    xchg   al,ah            ; Repeat for input AL
BnryTest:  mov    di,OFFSET BnryList
           mov    cl,NUMBNRY+1     ; One extra for no-match
           repne  scasb            ; Scan for match
           mov    bx,cx            ; 0 to NUMBNRY
           mov    al,PrecVals[bx]  ; Get precedence 0 to 3
           cmp    ah,al            ; On exit from second call,
evalout:   ret                     ;   compares AH/AL levels
;-------------------------------------------------------------
; Expression parser, with re-entry point at top for UnryEval.
;-------------------------------------------------------------
EvalAL:    cmp    al,'('-16        ; UnryEval entry--need '('
           jne    evalabort        ; No?  Abort (CX zero)

Evaluate:  call   UnryEval         ; First operand to ST
evalloop:  lodsb                   ; Expect binary op or ')'
           cmp    al,')'-16
           je     evalout          ; Got parenthesis?  Exit

           call   BnryTest         ; Insure binary operator
           jcxz   evalabort        ; Not binary op?  Abort

           dec    si               ; Point back for BnryEval
           call   BnryEval         ; Else handle one level
           jmp    SHORT evalloop   ; Loop at lower level
;-------------------------------------------------------------
; Binary expression parser.  Recurse left to right for ops at
; current or higher precedence level.  Context (mainly ST
; reals) on stack for recursion.  Stack use max for program is
; 0.7K, e.g., for 1+1*1^(1+1*1^(...)), limited to 127 bytes.
;-------------------------------------------------------------
BnryEval:  sub    sp,10            ; Reserve space for real
bnryloop:  mov    bx,sp            ; Pop ST from FPU and store
           fstp   TBYTE PTR [bx]   ;   to CPU stack
           lodsb                   ; Get confirmed binary op
           push   ax               ; Save to stack
           call   UnryEval         ; Get second operand
retest:    pop    ax               ; Refresh current op AL
           mov    ah,[si]          ; Possible next op
           push   ax               ; Save both ops
           call   GetPrec          ; Compare precedences
           jbe    samelevel        ; Lower or same?  Ahead

           call   BnryEval         ; Else recurse
           jmp    SHORT retest     ; Retest (* in 1+2^3*4)

samelevel: pop    ax               ; Discard ops
           MOVE   ax,bx            ; Index from GetPrec
           mov    bx,sp            ; Restore real from memory
           fld    TBYTE PTR [bx]   ;   and push back to ST
           fxch                    ; Next index is 1-based
           mov    bx,OFFSET BnryTbl-1
           pushf                   ; Flags from last GetPrec
           call   DoFunc           ; Perform binary operation
           popf                    ; Restore carry
           jnc    bnryloop         ; Next at same level?  Loop

           add    sp,10            ; Else lower precedence
           ret                     ; Clear stack and exit
;-------------------------------------------------------------
; Unary expression parser.  Recurse right to left internally.
; Also recurse to EvalAL if neither unary symbol nor number.
;-------------------------------------------------------------
UnryEval:  mov    cl,NUMCON+NUMUNRY; Size of symbol list
           mov    di,OFFSET UnryList
           xor    ax,ax            ; Dummy start--AH also zero
scanlp:    push   cx               ;   for later
           add    di,ax            ; To symbol length byte
           mov    cl,[di]          ; Length to CX
           inc    di               ; To symbol
           pusha                   ; 286--for DI/SI/CX here
           repe   cmpsb            ; Equality on match
           popa                    ; 286
           MOVE   ax,cx            ; Symbol length
           pop    cx               ; Count of symbols left
           loopne scanlp           ; No match?  Loop

           mov    di,OFFSET TempBuf; For ASCToFlt
           jne    chknum           ; No match?  Ahead

           add    si,ax            ; Advance past symbol
           push   cx               ; Save table index
           cmp    cl,NUMUNRY       ; Check for constant
           jae    skipunry         ; Yes?  Then skip next

           call   UnryEval         ; Recurse for operand
skipunry:  pop    ax               ; Table index (AH zero)
           mov    bx,OFFSET UnryTbl; Table offset
DoFunc:    xlatb                   ; Get difference byte
           add    ax,OFFSET RndNum ; Point AX to *Num
           jmp    ax               ; To *Num function

chknum:    lodsb                   ; Fetch character
           cmp    al,10            ; Test if digit
           jc     ASCToFlt         ; Yes?  Ahead

           cmp    al,'.'-16        ; Test if decimal point
           jne    EvalAL           ; No?  Recurse for '('
;-------------------------------------------------------------
; ASCII to ST routine.  Advances SI, may adjust BP, and uses
; TempBuf/TempBCD.  Effort made to detect integer so final
; rounding can improve accuracy.
;-------------------------------------------------------------
ASCToFlt:  push   di               ; Save TempBuf pointer
           cwd                     ; Zero DH/DL--BH/BL ignored
           call   GetDgtsAL        ; Ordinals to DI
           xor    bx,bx            ; Zero BL/BH count
           xchg   dl,dh            ; Save count DH, zero DL
           cmp    al,'.'-16        ; Test if decimal point
           jne    nodecm           ; No?  Ahead to adjust SI

           mov    ah,dh            ; Leading zero flag
           call   GetDgts          ; Decimals to DI
           mov    al,dl            ; Sum decimal counts for
           add    al,bl            ;   fixed notation intent
           cmp    al,FIXMAX        ; Compare to max allowed
           jb     fixcomp          ; Below?  Ahead

           mov    al,FIXMAX        ; Else cap at FIXMAX
fixcomp:   cbw                     ; Zero AH
           cmp    ax,bp            ; Compare to prior max
           jb     nochg            ; Smaller or E-notation?

           MOVE   bp,ax            ; Else adjust fixed length
nochg:     sub    dl,bh            ; Reduce decimal count
nodecm:    dec    si               ; Point back, possibly to E
           mov    cl,9
           xor    ax,ax            ; Append binary zeroes to
           rep    stosw            ;   insure 18 digits
           pop    di               ; Pointer to copied digits
           xchg   ax,bx            ; Zero BX and set AX to
           cbw                     ;   lead decimal zeroes
           push   dx               ; Save DH/DL/AX, implying
           push   ax               ;   decimal position
           push   si               ; Save til after loop
           mov    si,di            ; TempBuf--TempBCD precedes
           mov    cl,10            ; Loop stores zero then 9
           jmp    SHORT pkent      ;   BCD bytes leftward

pkloop:    lodsw                   ; Load two binary digits
           shl    al,4             ; 286-shift to pack AH/AL
           or     ah,al            ;   into AH as BCD
pkent:     dec    di               ; Move left
           mov    [di],ah          ; Save packed BCD byte
           loop   pkloop           ; Exit DI points to TempBCD

           pop    si               ; SI/BX/CX ready for
           lodsb                   ;   exponent accumulation
           cmp    al,'E'           ; Test if E-notation
           jne    gotexp           ; No?  Leave BX zero

           dec    cx               ; Anticipate '-' with -1
           lodsb                   ; Only time CH non-zero
           mov    bp,cx            ; Flag E-notation high bit
           cmp    al,'-'-16        ; Test if minus sign
           je     expnext          ; Got minus?  Enter loop

           inc    cx               ; Restore '+' flag
           cmp    al,'+'-16        ; Test if plus sign
           je     expnext          ; Got plus?  Enter loop
           jmp    SHORT expchk     ; Else expect digit

exploop:   cbw                     ; Zero AH
           xchg   ax,bx            ; Total to AX, saving digit
           mul    WORD PTR Ten     ; Times 10
           add    bx,ax            ; Accumulate to BX
expnext:   lodsb                   ; Get next possible digit
expchk:    cmp    al,10            ; If no digits, BX is zero
           jc     exploop          ; Digit?  Loop

gotexp:    dec    si               ; To byte after exponent
           jcxz   expplus          ; Plus flagged?  Ahead

           neg    bx               ; Negate--POP zeroes CH
expplus:   pop    cx               ; Lead decimal zero count
           sub    bx,cx            ; Adjust exponent
           pop    ax               ; Ordinal/decimal count
           mov    cl,al            ; Decimal digit count
           cmp    bx,cx            ; Test if integral ST
           pushf                   ; Save for rounding test
           mov    cl,ah            ; Ordinal digit count
           sub    bx,18            ; Adjust BX to imply 18
           add    bx,cx            ;   ordinal digits
           fbld   [di]             ; Packed BCD integer to ST
           call   FltTest          ; Test for zero ST
           je     chkrnd           ; Zero?  Ahead, ignoring BX

           call   LgNum            ; Log 2 of ST
           mov    [di],bx          ; Exponent to memory
           fldl2t                  ; Push log 2 of 10
           fimul  WORD PTR [di]    ; Times adjusted exponent
           fadd                    ; Sum logs, with one pop
           call   Raise2           ; Raise 2 to power ST
chkrnd:    popf                    ; Restore test result
           jl     notint           ; Not an integer?  Done
;-------------------------------------------------------------
; 25 *Num jump table routines.  0/1/2 operands on FPU stack.
; Positioning of routines embeds Ten/QMark in table.  ExpNum
; (x^y) retains sign of base x--non-standard, but simpler.
;-------------------------------------------------------------
RndNum:    frndint                 ; To nearest integer, even
notint:    ret                     ;   on tiebreak

ACosNum:   call   PrepArc          ; Push SQR(1 - ST*ST)
           fxch                    ; Swap
           jmp    SHORT atanup     ; Arctan of ST(1)/ST, pop

ASinNum:   call   PrepArc          ; Push SQR(1 - ST*ST)
           jmp    SHORT atanup     ; Arctan of ST(1)/ST, pop

ATanNum:   fld1                    ; Push 1
atanup:    fpatan                  ; Arctan of ST(1)/ST, pop
           ret

PrepArc:   fld1                    ; Push 1
           fld    st(1)            ; Push input for squaring
           fmul   st(0),st         ; Square ST--no pop
           fsub                    ; 1 less square, with pop
SqrNum:    fsqrt                   ; Square root to ST with
           ret                     ;   original ST in ST(1)

MemNum:    fld    TBYTE PTR Mem    ; Load prior ST saved at
           ret                     ;   program start
LgNum:     fld1                    ; Push 1
           fxch                    ; Swap ST and ST(1)
           fyl2x                   ; 1 times lg(input), with 1
           ret                     ;   discarded

LnNum:     call   LgNum
           fldl2e                  ; Push lg(e)
           jmp    SHORT DivNum     ; Ln(input), with discard

LogNum:    call   LgNum
           fldl2t                  ; Push lg(10)
           jmp    SHORT DivNum     ; Log(input), with discard

AbsNum:    fabs                    ; Absolute value
           ret

PiNum:     fldpi                   ; Load ã rounded to 80-bits
           ret

SinNum:    fsin                    ; No calc if |ST| >= 2^63,
           jmp    SHORT cosup      ;   but C2 flags error

CosNum:    fcos                    ; No calc if |ST| >= 2^63
cosup:     fld1                    ; Dummy if ok--preserves C2
           jmp    SHORT trigchk

TanNum:    fptan                   ; No calc if |ST| >= 2^63
trigchk:   fnstsw ax               ; Check if range error
           sahf
           jnp    DivNum           ; C2 clear?  Ahead

           fldz                    ; Else force exception
DivNum:    fdiv                    ; Divide for ratio
FltTest:   ftst                    ; Compare ST to +0
           fnstsw ax               ; Status C0/C2/C3 to CPU
           sahf                    ;   carry/parity/zero flags
           ret

ENum:      fldl2e                  ; FPU supplies lg(e), not e
           jmp    SHORT Raise2     ; 2^lg(e) is e

IntNum:    call   PrepInt          ; Remainder from 1-divide
SubNum:    fsub                    ; Subtract, with discard
           ret

AddNum:    fadd                    ; Add ST and ST(1) with pop
           ret

MulNum:    fmul                    ; Multiply with pop
           ret

SignNum:   fldz                    ; Zero exponent for -1/0/1
ExpNum:    fxch                    ; Swap
           call   FltTest          ; Test base zero/negative
           je     MulNum           ; Zero?  Force zero result
           jnc    RaiseXtoY        ; Positive base?  Ahead

           fabs                    ; Else keep base sign
           call   RaiseXtoY        ; Raise ST(1) to power ST
NegNum:    fchs                    ; Change sign (unary)
PosNum:    ret                     ; Plus sign (unary)

PrepInt:   fld    st               ; Function pushes ST % 1
           fld1
ModNum:    fxch                    ; Function does ST(1) % ST
mnloop:    fprem                   ; Chop toward zero
           fnstsw ax               ; Partial if exponents
           sahf                    ;   differ by 64+
           jp     mnloop           ; C2 set?  Loop til done
           jmp    SHORT rxpop      ; Ahead to discard ST(1)
;-------------------------------------------------------------
; Several exponent routines next.  F2XM1 needs |ST| <= 1.
; RaiseIf,  Raise10CX, and Raise10 called only by FltToASC.
;-------------------------------------------------------------
RaiseIf:   jge    riup             ; Pushes 10^[SI] or 10^CX

Raise10CX: mov    [si],cx          ; Pushes 10^CX to ST
riup:      fild   WORD PTR [si]
Raise10:   fild   WORD PTR Ten
RaiseXtoY: fyl2x                   ; ST(1) times lg(ST), pop
Raise2:    call   PrepInt          ; Remainder from 1-divide
           f2xm1                   ; Raise 2 to result, less 1
           fld1
           fadd                    ; Add 1, discarding 1
           fscale                  ; Scale by ST(1), auto-
rxpop:     fxch                    ;   chopped toward zero
           fstp   st               ; Discard scale value
           ret
;-------------------------------------------------------------
; Copy digits at SI to DI, setting counts DL/BH/BL--digits/
; trailing 0's/leading 0's.  AH zero flags BL count without
; store.  Counts help spot integers like 10.00 and allow
; accurate load of input like 0.000000000000000000123456789.
;-------------------------------------------------------------
gdloop:    test   al,al            ; Test if zero
           jne    gd1to9           ; Not zero?  Ahead

           test   ah,ah            ; Test if BL count on
           jne    gdtrail          ; No?  Bump tail zero count

           inc    bx               ; Else bump lead zero count
           jmp    SHORT GetDgts    ;   and skip rest

gd1to9:    mov    ah,-1            ; Kill flag for BL bumps
           mov    bh,-1            ; Reset trailing zero count
gdtrail:   inc    bh               ; Bump trailing zero count
           inc    dx               ; Bump count DL
           stosb
GetDgts:   lodsb
GetDgtsAL: cmp    al,10            ; Test if digit
           jc     gdloop           ; Yes?  Loop

gdout:     ret
;-------------------------------------------------------------
; Convert ST to ASCII at DI.  BP flags output decimals or
; E-notation.  Latter forced if large result.  0 or -0 forced
; if tiny result and not E-notation.  Maximum precision is 16
; (not 19) digits for slop and simpler coding.  Exponent still
; allowed to 4931 FPU limit.
;-------------------------------------------------------------
FltToASC:  mov    dx,'  '          ; Anticipate positive
           call   FltTail          ; Handle fixed output
           jns    gdout            ; No E-notation?  Done

           mov    [si],bx          ; Else push exponent BX
           fild   WORD PTR [si]    ;   to ST for second call
           mov    dx,'+E'          ; E then output sign
FltTail:   xor    bx,bx            ; Zero BX in case ST zero
           call   FltTest          ; Test for zero/sign
           jnc    ftnsign          ; ST non-negative?  Ahead

           mov    dh,'-'           ; Else use '-' for sign
ftnsign:   MOVE   ax,dx
           stosw                   ; Store space/E then sign
           je     ftnstore         ; ST zero?  Ahead

           fabs                    ; Force positive
           call   LogNum           ; Log 10 of ST and test
           fld    st               ; Extra ST copy for FADD
           jnc    ftnup            ; Not negative?  Ahead

           fld1                    ; Else less 1 so next
           fsub                    ;   chops toward -infinity
ftnup:     call   IntNum           ; Chop toward zero
           fist   WORD PTR [si]    ; Store tentative exponent
           mov    bl,17            ; Implied decimal digits
           xchg   bx,[si]          ; Swap int() and BX
           fchs                    ; Change ST sign
           fiadd  WORD PTR [si]    ; 17 less exponent
           fadd                    ; Plus log() pushed earlier
           call   Raise10          ; 10^ST
           mov    cl,2             ; Minimum rounding
           cmp    bp,cx            ; Test if E-notation
           jl     ftnmin           ; Yes?  Use minimum round

           sub    [si],bp          ; 17 less BP
           sub    [si],bx          ; Less tentative exponent
           cmp    [si],cx          ; Compare to min for next
ftnmin:    call   RaiseIf          ; Push 10^[SI] or 10^CX
           frndint                 ; Make exact if small power
           fdiv   st(1),st         ; Divide without pop
           fxch                    ; Swap quotient to ST
           frndint                 ; Round to nearest integer
           fmul                    ; Complete rounding
           mov    cl,18            ; Construct limit between
           call   Raise10CX        ;   10^18-0.5 and 10^18
           fcomp                   ; Compare, discarding 10^18
           fnstsw ax               ; Need to cap FBLD output
           sahf                    ;   for rare 19-digit case
           ja     ftnstore         ; Already 18 digits?  Ahead

           fidiv  WORD PTR Ten     ; Else scale back ST and BX
           inc    bx               ;  for, e.g., 0.1 input
ftnstore:  fbstp  [si]             ; Store BCD digits
           xor    cx,cx            ; Anticipate no prefix 0's
           test   bp,bp            ; Recheck E-notation flag
           js     ftndump          ; E-notation?  Ahead

           mov    ax,FIXMAX        ; Used twice below
           test   bx,bx
           js     ftnneg           ; Negative exponent?  Ahead

           shl    bp,1             ; Discard high bit and set
           cmp    ax,bx            ;   carry if large exponent
           rcr    bp,1             ; Insert E-notation flag
           jmp    SHORT ftndump

ftnneg:    neg    bx               ; Make exponent positive
           cmp    bx,bp            ; Compare to fixed length
           jna    ftnok            ; Small?  Ahead

           MOVE   bx,ax            ; Cap negative exponent
ftnok:     mov    cx,bx            ; Number of prefix 0's
           neg    bx               ; Restore sign
ftndump:   mov    al,'0'           ; If negative exponent,
           rep    stosb            ;   prefix with 0's
           mov    cl,9             ; Loop unpacks 18 digits
           add    si,cx            ; Point to high byte (zero)
unpkloop:  dec    si               ; At start, skips high byte
           cbw                     ; Zero AH
           mov    al,[si]          ; Get two BCD digits
           ror    ax,4             ; 286-shift nibble to AL
           shr    ah,4             ; 286-shift nibble to AH
           or     ax,'00'          ; To ASCII digit pair
           stosw                   ; Exit AL is '0' due to
           loop   unpkloop         ;   minimum rounding

           mov    cl,TAILZERO
           rep    stosb            ; Pad trailing ASCII 0's
           sub    di,18+TAILZERO
           mov    cl,17            ; E-notation decimals
           test   bp,bp            ; Test if E-notation
           push   cx               ; Save exit BP and
           pushf                   ;   result of test
           js     ftndot           ; E-notation?  Ahead

           mov    cx,bp            ; Else adjust CX/DI for
           add    di,bx            ;   fixed notation
ftndot:    mov    al,'.'           ; Store dot first
           inc    cx               ; Account for dot
riploop:   inc    di               ; Insert dot and ripple
           xchg   [di],al          ;   digits right with '0'
           loop   riploop          ;   in exit AL

           mov    cl,128           ; Plenty for scans
           std                     ; Reverse direction
           repe   scasb            ; Scan over trailing zeroes
           inc    di               ; Will rescan last byte
           mov    al,'.'
           repe   scasb            ; Scan over possible dot
           cld                     ; Restore program default
           scasw                   ; Bump DI by 2
           popf                    ; E-notation test result
           pop    bp               ; Small positive
           ret
;-------------------------------------------------------------
; Constant data.  Dual use overwrites some data when no longer
; needed.  Code/data were tightened to keep COM small but with
; reasonable syntax.  Function list omitted from syntax, but
; can view list at COM tail.
;-------------------------------------------------------------
UnryTbl    DB     POSDIFF, NEGDIFF, ABSDIFF
           DB     SQRDIFF, ATANDIFF,ACOSDIFF
Ten        DB     ASINDIFF,RNDDIFF, SIGNDIFF
           DB     INTDIFF, TANDIFF, COSDIFF
QMark      DB     SINDIFF, LGDIFF,  LNDIFF
           DB     LOGDIFF, MEMDIFF, PIDIFF,  EDIFF

BnryTbl    DB     EXPDIFF, MODDIFF, DIVDIFF
           DB     MULDIFF, SUBDIFF, ADDDIFF

PrecVals   DB     0,3,2,2,2,1      ; Includes following 1

UnryList   DB     1,"E",   2,"PI",  3,"MEM", 3,"LOG"
           DB     2,"LN",  2,"LG",  3,"SIN", 3,"COS"
           DB     3,"TAN", 3,"INT", 4,"SIGN",3,"RND"
           DB     4,"ASIN",4,"ACOS",4,"ATAN",3,"SQR",3,"ABS"
           DB     1,"-"-16,1       ; Includes following '+'

BnryList   DB     "+"-16,"-"-16,"*"-16,"/"-16,"%"-16,"^"

Syntax     =      $                ; Also FPU state
Mem        =      $ + 14           ; ST from prior run
TempBCD    =      $ + 24           ; Temp BCD storage
TempBuf    =      $ + 34           ; Temp use buffer

           DB     "Need 80387: CALC387 [math_expression] "
           DB     " CRH/28 Jan 96",13,10
           DB     "E.g., 1+4*2^3-5/4%1, 1E0/7, (MEM+1)^1.5, "
           DB     "or E*COS(PI)","$"

Code_Seg   ENDS
           END Calc387
