home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power-Programmierung
/
CD1.mdf
/
magazine
/
pcmagazi
/
1990
/
10
/
compute.asm
next >
Wrap
Assembly Source File
|
1990-01-27
|
44KB
|
1,017 lines
;--------------------------------------------------------------------;
; COMPUTE * PC Magazine * Michael J. Mefford ;
; Command line five function math calculator. ;
;--------------------------------------------------------------------;
_TEXT SEGMENT PUBLIC 'CODE'
ASSUME CS:_TEXT,DS:_TEXT,ES:_TEXT,SS:_TEXT
ORG 100H
START: JMP MAIN
; DATA AREA
; ---------
SIGNATURE DB CR,SPACE,SPACE,SPACE,CR,LF
COPYRIGHT DB "COMPUTE 1.0 (C) 1990 Ziff Communications Co.",CR,LF
PROGRAMMER DB "PC Magazine ",BOX," Michael J. Mefford",CR,LF,LF,"$"
SYNTAX DB "Syntax: COMPUTE arithmetic expression",CR,LF
DB "Operators supported are + - * / % ( ) [ ] 0-9 x",CR,LF
DB "Note: % = modulo, remainder of integer division.",CR,LF
DB " x = answer of last compute."
CRLFLF DB LF
CRLF DB CR,LF
DB "$",CTRL_Z
CR EQU 13
LF EQU 10
CTRL_Z EQU 26
SPACE EQU 32
BOX EQU 254
COMMA EQU ","
INTEGER_PRECISION = 20
DECIMAL_PRECISION = 20
PRECISION = INTEGER_PRECISION + DECIMAL_PRECISION
NUMBER STRUC
SIGN DB 0
INTEGER DB INTEGER_PRECISION DUP (0)
DECIMAL DB DECIMAL_PRECISION DUP (0)
NUMBER ENDS
SIZE_MANTISSA = SIZE INTEGER + SIZE DECIMAL
SIZE_DIVIDEND = SIZE_MANTISSA + SIZE DECIMAL + 1
OPERAND DW INITIAL_VALUE
OPERATORS DB "+-"
UNARY_PREFIXES LABEL BYTE
PRECEDENCE DB "*/%"
PRECEDENCE_CNT = $ - PRECEDENCE
OPERATOR_CNT = $ - OPERATORS
DB "([",0
PREFIXES_CNT = $ - UNARY_PREFIXES
LAST_CHAR DB ?
PAREN_CNT DW 0
PAREN_TYPE DB 50 DUP (0)
WORKSPACE DB 2 * SIZE_MANTISSA DUP (?)
WORKSPACE_END EQU $ - 1
DB ? ;Dividend overflow.
DIVIDEND DB SIZE_DIVIDEND DUP (?)
ANSWER DB SIZE NUMBER DUP (0)
DECIMAL_PLACES DW 0 ;Significant decimal digits.
SAVE_SIGN DB ?
MODULO_FLAG DB 0 ; =1 if modulo division.
COMPUTE DB "COMPUTE.COM",0
COMPUTE_LEN EQU $ - COMPUTE
PATH DB "PATH="
PATH_LEN EQU $ - PATH
NOT_FOUND DB CR,LF,"Could not save answer in Compute.com",CR,LF
DB "Compute.com must be in current directory",CR,LF
DB "or in DOS PATH= directory.",CR,LF,"$"
RESULT_MSG DB "Result is $"
UNRECOGNIZED DB "Unrecognized character$"
MISSING_PAREN DB "Missing parenthesis or bracket$"
UNMATCHED DB "Miss-matched parenthesis with bracket$"
ZERO_DIVIDE DB "Divide by zero$"
OVERFLOW DB "Overflow$"
LOGICAL DB "Logic error$"
MEMORY DB "Requires 10K of memory$"
ERROR1 DB "Error ^",CR,LF,"$"
ERROR2 DB "^ Error$",CR,LF,"$"
; CODE AREA
; ---------
MAIN PROC NEAR
CLD ;Strings forward.
MOV DX,OFFSET SIGNATURE ;Announce ourselves.
CALL PRINT_STRING
MOV BX,10 / 16 * 1024 ;Minimum of 10K required.
MOV AH,4AH ;Request via DOS.
INT 21H
MOV DX,OFFSET MEMORY
JC ERROR_EXIT ;Exit with message if not enough.
MOV SP,16 * 1024 - 2 ;Else, setup stack at end of seg.
MOV SI,81H ;Point to command line.
XOR AX,AX ;No characters yet.
XOR DX,DX ;Not unary.
CALL PARSE ;Parse command line.
CALL PARSE_DELIMIT ;See if reached end.
MOV DX,OFFSET LOGICAL ;If not, logical error.
JNC ERROR_EXIT
CMP OPERAND,OFFSET INITIAL_VALUE
JNZ CK_PARENS
MOV DX,OFFSET SYNTAX ;Did we get a value.
CALL PRINT_STRING ;If not, print syntax and exit.
JMP SHORT ERROR_END
CK_PARENS: MOV DX,OFFSET MISSING_PAREN
CMP PAREN_CNT,0 ;Did we match all parenthesis
JNZ ERROR_EXIT ; and brackets?
CALL RESULT ;Every thing seems OK so
; display result.
EXIT: MOV AX,4C00H ;Exit with ErrorLevel=0
INT 21H
MAIN ENDP
;----------------------------------------------;
ERROR_EXIT: PUSH DX ;Save error message.
MOV DX,OFFSET SYNTAX ;Display syntax.
CALL PRINT_STRING
POP DX
CALL PRINT_STRING ;Display error.
MOV DX,OFFSET CRLFLF
CALL PRINT_STRING
MOV BX,SI ;Current command line pointer.
MOV SI,81H ;Point at start of command line.
CLD ;Make strings are forward.
NEXT_LINE: MOV CX,79 ;1 less than 80 char. display.
NEXT_ERROR: LODSB
CMP AL,CR ;End of command line?
JZ PRINT_POINTER
CALL PRINT_CHAR ;If no, print character.
CMP SI,BX ;Pointing to error position?
JZ PRINT_POINTER ;If yes, done here.
LOOP NEXT_ERROR ;Else, continue.
MOV DX,OFFSET CRLF ;Filled on line; new line.
CALL PRINT_STRING
JMP NEXT_LINE
PRINT_POINTER: MOV DX,OFFSET CRLF ;New line.
CALL PRINT_STRING
SUB CX,79 ;Calculate chars. on line above.
NEG CX
MOV BX,OFFSET ERROR1 ;Assume left side of display.
CMP CX,40
JB LEFT_ARROW
SUB CX,6
JMP SHORT NEXT_SPACE
LEFT_ARROW: MOV BX,OFFSET ERROR2
NEXT_SPACE: MOV AL,SPACE ;Pad with spaces over to error.
CALL PRINT_CHAR
LOOP NEXT_SPACE
MOV DX,BX ;Display "Error ^".
CALL PRINT_STRING
ERROR_END: MOV AX,4C01H ;Exit with ErrorLevel = 1.
INT 21H
;----------------------------------------------;
; Recursive parsing routine. ;
;----------------------------------------------;
PARSE: PUSH BP ;Preserve stack index.
PUSH DX ;Unary flag.
PUSH AX ;Operator.
MOV BP,SP ;Point to local vars.
NEXT_PARSE: CALL PARSE_DELIMIT ;Parse white space.
JNC GET_CHAR ;If command line end, exit.
JMP PARSE_END
GET_CHAR: LODSB ;Else, get the character.
MOV DI,OFFSET OPERATORS ;Is it a function operator?
MOV CX,OPERATOR_CNT
REPNZ SCASB
JNZ CK_NUM ;If no, check if number.
XOR DL,DL ;Assume not unary.
CMP AL,"+" ;Is it plus or minus?
JZ CK_UNARY
CMP AL,"-"
JNZ GET_OPERAND2 ;If no, not unary.
CK_UNARY: PUSH AX
MOV AL,LAST_CHAR ;Else, does previous char
MOV DI,OFFSET UNARY_PREFIXES ; suggest unary?
MOV CX,PREFIXES_CNT
REPNZ SCASB
POP AX
JNZ GET_OPERAND2
INC DL ;If yes, it's unary.
GET_OPERAND2: PUSH WORD PTR LAST_CHAR ;Save last char.
MOV LAST_CHAR,AL ;Store current char.
PUSH OPERAND ;Save operand pointer.
CALL PARSE ;Get second operand.
POP BX ;First operand.
POP CX ;Last character.
MOV DH,AL ;Save operator.
MOV DI,OPERAND ;Second operand.
MOV AL,[DI] ;Get signs.
MOV AH,[BX]
CMP DH,"*" ;Check which operator.
JZ DO_MULT
CMP DH,"/"
JZ DO_DIV
CMP DH,"%"
JZ DO_MOD
;Must be plus or minus.
OR DL,DL ;Was it unary.
JZ DO_ADD ;If no, add operands.
MOV AL,CL ;Else, was last character
CALL CK_PRECEDENCE ; "*", "/" or "%" ?
JNZ NEXT_PARSE ;If no, just continue parsing.
JMP PARSE_END ;Else, return second operand.
DO_ADD: CALL ADD_IT ;Add operands.
MOV AL,LAST_CHAR
CMP AL,")" ;Is this an equantity?
JZ PARSE_DONE ;Is yes, return it.
CMP AL,"]"
JZ PARSE_DONE
JMP NEXT_PARSE ;Else, continue parsing.
DO_MULT: CALL MULTIPLY
JMP NEXT_PARSE
DO_DIV: CALL DIVIDE
JMP NEXT_PARSE
DO_MOD: CALL MODULO
JMP NEXT_PARSE
PARSE_DONE: JMP PARSE_END ;Lilly pad for return.
CK_NUM: MOV LAST_CHAR,AL ;Store current character.
CMP AL,"X" ;Is it "answer" character?
JZ DO_ANS
CMP AL,"x"
JNZ CK_DIGIT
DO_ANS: PUSH SI
MOV SI,OFFSET ANSWER ;If yes copy answer to operand.
ADD OPERAND,SIZE NUMBER ;Point to next operand storage.
MOV DI,OPERAND
PUSH DI
MOV CX,SIZE NUMBER
REP MOVSB
POP DI
MOV AL,[BP] ;Get sign
CMP AL,"-"
JZ ANS_SIGN
XOR AL,AL ; and store.
ANS_SIGN: XOR [DI],AL
POP SI
JMP SHORT CK_UNARY_NUM ;Check if return or not.
CK_DIGIT: CMP AL,"." ;Is char a num char?
JZ DO_NUM
CMP AL,"0"
JB CK_PAREN
CMP AL,"9"
JA CK_PAREN
DO_NUM: DEC SI ;If yes, point to it.
ADD OPERAND,SIZE NUMBER ;Point to next operand storage.
CALL GET_NUMBER ;Get the number.
CK_UNARY_NUM: CMP BYTE PTR [BP + 2],1 ;Is this a unary number?
JZ PARSE_END ;If yes, return the number.
MOV AL,[BP] ;Get operator.
CALL CK_PRECEDENCE ;If high precedence, go do it.
JZ PARSE_END
JMP NEXT_PARSE ;Else, continue parsing.
CK_PAREN: MOV AH,")"
CMP AL,"(" ;Is it an open parenthesis
JZ FOUND_PAREN ; or bracket?
CMP AL,AH
JZ CK_OPEN
MOV AH,"]"
CMP AL,"["
JZ FOUND_PAREN
CMP AL,AH ;Was it a close parenthesis or
MOV DX,OFFSET UNRECOGNIZED ; or bracket?
JNZ PARSE_ERROR ;If no, must be invalid char.
CK_OPEN: DEC PAREN_CNT ;If close, decrement level.
MOV DX,OFFSET MISSING_PAREN ;If overflow then had no
JC PARSE_ERROR ; open.
MOV BX,PAREN_CNT ;Retrieve level index
MOV PAREN_TYPE[BX],AH ; and store close type.
JMP SHORT PARSE_END ;Return results.
FOUND_PAREN: PUSH PAREN_CNT ;Save level index.
INC PAREN_CNT ;Next level.
XOR DX,DX ;Not unary flag.
CALL PARSE ;Parse the equantity.
POP BX ;Retrieve level.
CMP AH,PAREN_TYPE[BX] ;Did we close with matching
MOV DX,OFFSET UNMATCHED ; parenthesis or bracket?
JNZ PARSE_ERROR
MOV AL,[BP] ;Retrieve, prefix operator.
CALL CK_PRECEDENCE ;Was it priority operator?
JZ PARSE_END ;If yes, return value.
CMP AL,"-" ;Else store sign.
JNZ PAREN_END
MOV BX,OPERAND
XOR [BX],AL
PAREN_END: JMP NEXT_PARSE ;And parse next.
PARSE_END: POP AX
POP DX
POP BP
RET
PARSE_ERROR: JMP ERROR_EXIT
RET
;-------------------------------------------------------------------;
; INPUT: SI -> string ;
; OUTPUT SI -> first non-white space and CY=0 or SI -> CR and CY=1. ;
;-------------------------------------------------------------------;
PARSE_DELIMIT: LODSB ;Get a byte.
CMP AL,CR
JZ STRING_END
CMP AL,SPACE ;Is it a space char or below?
JBE PARSE_DELIMIT
DEC SI ;Else, adjust pointer to
CLC
RET ; string start.
STRING_END: DEC SI
STC
RET
;----------------------------------------------;
CK_PRECEDENCE: MOV DI,OFFSET PRECEDENCE
MOV CX,PRECEDENCE_CNT
REPNZ SCASB
RET
;----------------------------------------------;
GET_NUMBER: MOV DI,OFFSET WORKSPACE ;Store integer temporarily
MOV CX,SIZE INTEGER + 1 ; so can store right justified.
NEXT_INTEGER: LODSB
CMP AL,COMMA ;Ignore commas.
JZ NEXT_INTEGER
CMP AL,"." ;Decimal point; end of integer.
JZ STORE_INTEGER
GET_NUM2: SUB AL,"0"
JC INTEGER_END
CMP AL,9
JA INTEGER_END
STOSB ;Store the number.
LOOP NEXT_INTEGER
MOV DX,OFFSET OVERFLOW ;If got here, number too big.
JMP ERROR_EXIT
INTEGER_END: DEC SI
STORE_INTEGER: PUSH SI ;Save command line pointer.
MOV SI,DI
DEC SI ;End of workspace.
PUSHF
STD
MOV DI,OPERAND
ADD DI,SIZE INTEGER
MOV DX,CX
DEC DX ;Zero pad count.
NEG CX
ADD CX,SIZE INTEGER + 1
JCXZ PAD_INTEGER
REP MOVSB ;Store integer part in operand.
PAD_INTEGER: MOV CX,DX
JCXZ INTEGER_DONE
XOR AL,AL
REP STOSB ;Pad balance of integer with 0.
INTEGER_DONE: MOV AL,[BP] ;Get sign
CMP AL,"-"
JZ INT_SIGN
XOR AL,AL ; and store.
INT_SIGN: STOSB
POPF
DO_DECIMAL: POP SI ;Restore command line pointer.
MOV DI,OPERAND
ADD DI,SIZE SIGN + SIZE INTEGER ;Decimal operand storage.
MOV CX,SIZE DECIMAL + 1 ;Left justified.
XOR DX,DX ;Decimal counter.
NEXT_DECIMAL: LODSB
SUB AL,"0"
JC DECIMAL_END
CMP AL,9
JA DECIMAL_END
STOSB
INC DX ;Increment decimal count.
LOOP NEXT_DECIMAL
MOV DX,OFFSET OVERFLOW ;If got here, number too big.
JMP ERROR_EXIT
DECIMAL_END: DEC SI ;Adjust command line pointer.
CMP DX,DECIMAL_PLACES ;Store significant decimal
JBE PAD_DECIMAL ; places.
MOV DECIMAL_PLACES,DX
PAD_DECIMAL: DEC CX ;Pad balance with zeros.
JCXZ DECIMAL_DONE
XOR AL,AL
REP STOSB
DECIMAL_DONE: RET
;----------------------------------------------;
; INPUT: BX -> 1st and AH=sign; DI -> 2nd and AL=sign.
; OUTPUT: Result stored in DI.
ADD_IT: PUSHF
PUSH SI
STD ;Add or subtract right to left.
MOV SAVE_SIGN,0 ;Assume sign positive.
MOV DX,BX ;Save operand 2 pointer.
MOV CX,SIZE_MANTISSA ;Point to end of operands.
ADD DI,CX
ADD BX,CX
MOV SI,BX
CMP AL,AH ;If both plus or minus, then
JZ ADD_SIGN ; go ahead and add, else subtract
CMP AL,"-" ;If first operand
JZ SUB_IT ; negative and second positive
XCHG DI,SI ; swap operands before subtract.
MOV OPERAND,DX
SUB_IT: MOV BX,DI ;Save destination operand.
CLC ;Start with no carry.
NEXT_SUB: LODSB ;Get operand1
SBB AL,[DI] ;Subtract operand2 and the carry.
AAS ;ASCII adjust after subtraction.
STOSB ;Store it.
LOOP NEXT_SUB ;Subtract next byte.
MOV AL,0 ;If no carry, then positive.
JNC STORE_SIGN
MOV DI,BX ;Else negative so negate by
CLC ; subtracting result from zero.
MOV CX,SIZE_MANTISSA
NEXT_NEGATE: MOV AL,CH ;Zero.
SBB AL,[DI] ;Subtract the byte.
AAS ;ASCII adjust after subtraction.
STOSB ;Store it.
LOOP NEXT_NEGATE
MOV AL,"-" ;Store negative sign.
JMP SHORT STORE_SIGN
; Add procedure.
;----------------------------------------------;
ADD_SIGN: MOV SAVE_SIGN,AL ;Either 2 +'s or 2 -'s; save it.
CLC ;Start with no carry.
NEXT_ADD: LODSB
ADC AL,[DI] ;Add operand two and carry.
AAA ;ASCII adjust.
STOSB
LOOP NEXT_ADD
JC ADD_ERROR ;If carry out of number, overflow
MOV AL,SAVE_SIGN ;Else, store sign.
STORE_SIGN: STOSB
POP SI
POPF
RET
ADD_ERROR: MOV DX,OFFSET OVERFLOW
POP SI
JMP ERROR_EXIT
;----------------------------------------------;
; INPUT: BX -> 1st and AH=sign; DI -> 2nd and AL=sign.
; OUTPUT: Result stored in DI.
MULTIPLY: PUSH BP
PUSHF
PUSH SI
XOR DL,DL ;Assume plus result.
CMP AL,AH ;Signs the same?
JZ MULT_SIGN ;If yes, assumed right.
MOV DL,"-" ;Else, negative.
MULT_SIGN: MOV SAVE_SIGN,DL
STD ;Right to left.
ADD DI,SIZE_MANTISSA ;Point to end of numbers.
ADD BX,SIZE_MANTISSA
MOV BP,DI ;Save destination.
MOV DI,OFFSET WORKSPACE_END
MOV DX,DI
XOR AX,AX ;Zero out scratch pad area.
MOV CX,SIZE WORKSPACE
REP STOSB
MOV DI,DX ;Point to workspace.
MOV CX,SIZE_MANTISSA ;Number of digits to multiply.
NEXT_MULT: PUSH DI ;Save current product place.
MOV AL,[BX] ;Current multiplier digit zero?
OR AL,AL
JZ LOOP_MULT ;If yes, no need to multiply.
PUSH CX ;Else, save multiplier counter.
MOV CX,SIZE_MANTISSA ;Size of multiplicand size.
MOV DL,AL ;Multiplier.
MOV SI,BP ;Multiplicand pointer.
XOR DH,DH ;No carry yet.
NEXT_MULT2: LODSB
MUL DL ;Multiply them.
AAM ;Adjust BCD multiplication.
ADD AL,[DI] ;Accumulate as we go.
AAA ;ASCII adjust.
ADD AL,DH ;Add the carry.
AAA ;ASCII adjust.
STOSB ;And store.
MOV DH,AH ;Save the carry.
LOOP NEXT_MULT2
MOV [DI],AH ;Store the carry.
POP CX ;Multiply by all digits.
LOOP_MULT: POP DI
DEC BX ;Next multiplier.
DEC DI ;Next product.
LOOP NEXT_MULT
MOV DI,OFFSET WORKSPACE_END - SIZE DECIMAL + 1
MOV CX,SIZE_MANTISSA
CALL ROUND ;Round up product.
MOV SI,OFFSET WORKSPACE_END - SIZE DECIMAL
MOV DI,BP ;Place decimal in product
MOV CX,SIZE_MANTISSA ; by storing in destination.
REP MOVSB
MOV AL,SAVE_SIGN ;Add the sign.
STOSB
MOV CX,SIZE INTEGER ;Check for overflow.
NEXT_OVER: LODSB
OR AL,AL
JNZ MULT_ERROR
LOOP NEXT_OVER
POP SI
POPF
POP BP
RET
MULT_ERROR: MOV DX,OFFSET OVERFLOW
POP SI
JMP ERROR_EXIT
;----------------------------------------------;
; INPUT: BX -> 1st and AH=sign; DI -> 2nd and AL=sign.
; OUTPUT: Result stored in DI.
DIVIDE: PUSH BP
PUSHF
PUSH SI
XOR DL,DL ;Assume plus result.
CMP AL,AH ;Signs the same?
JZ DIV_SIGN ;Is yes, assumed right.
MOV DL,"-" ;Else, negative.
DIV_SIGN: MOV SAVE_SIGN,DL
MOV BP,DI ;BP -> divisor.
INC BP ;Bump past sign.
MOV DI,OFFSET WORKSPACE ;Quotient scratch pad.
XOR AL,AL
MOV CX,SIZE_DIVIDEND
REP STOSB ;Zero out quotient to start.
MOV DI,OFFSET DIVIDEND - 1 ;Move dividend to scratch pad.
STOSB ;Zero in overflow byte.
MOV SI,BX ;Dividend.
INC SI ;Bump past sign.
MOV CX,SIZE_MANTISSA
REP MOVSB
MOV CX,SIZE_DIVIDEND - SIZE_MANTISSA
REP STOSB ;Zero out dividend underflow.
MOV DI,BP ;Look for zero divisor.
MOV CX,SIZE_MANTISSA
REPZ SCASB
JNZ DIVISOR_SIZE
MOV DX,OFFSET ZERO_DIVIDE ;Can't divide by zero.
POP SI ;Command line pointer.
JMP ERROR_EXIT
DIVISOR_SIZE: MOV DI,OFFSET WORKSPACE ;Quotient.
ADD DI,CX ;Point to first quotient storage.
ADD BP,SIZE_MANTISSA - 1
SUB BP,CX ;First non-zero divisor digit.
INC CX ;DH = subtraction counter.
MOV DX,CX ;DL = significant divisor digits.
MOV SI,OFFSET DIVIDEND
MOV CX,SIZE_DIVIDEND + 1
SUB CX,DX ;Number of times to divide.
NEXT_DIVIDE: CMP MODULO_FLAG,1 ;Is this a modulo division?
JNZ NO_MODULO
CMP DI,OFFSET WORKSPACE + SIZE_MANTISSA
JAE DIVIDE_END ;If yes, divide integer only.
NO_MODULO: PUSH CX ;Division count.
PUSH DI ;Quotient.
MOV BX,SI ;Save dividend pointer.
XOR DH,DH ;Initialize sub counter to zero.
NEXT_DIVIDE2: CMP BYTE PTR [SI - 1],0 ;Overflow in remainder?
JNZ SUB_DIVISOR ;If yes, divisor fits.
MOV CL,DL ;Divisor length.
XOR CH,CH ;Zero high half.
MOV DI,BP ;Divisor.
NEXT_DIV: CMPSB ;Does it fit in remainder?
JA SUB_DIVISOR ;If yes, subtract.
JB DIV_TOO_BIG ;If no, save subtraction count.
LOOP NEXT_DIV
SUB_DIVISOR: INC DH ;Sub counter.
MOV SI,BP ;Divisor position.
MOV DI,BX ;Dividend position.
MOV CL,DL ;Significant divisor digits.
XOR CH,CH ;Zero in high half.
DEC CX ;Adjust for a moment.
ADD SI,CX ;Point to current position.
ADD DI,CX
INC CX
STD ;Subtract right to left.
CLC ;Start with no carry.
NEXT_DIV_SUB: LODSB
MOV AH,AL ;Divisor digit.
MOV AL,[DI] ;Remainder digit.
SBB AL,AH ;Subtract.
AAS ;ASCII adjust. (BCD)
STOSB ;Store in dividend.
LOOP NEXT_DIV_SUB
SBB BYTE PTR [DI],0 ;Subtract the carry, if any.
CLD ;Back to forward.
MOV SI,BX
JMP NEXT_DIVIDE2 ;Next divisor.
DIV_TOO_BIG: MOV SI,BX ;Next dividend.
POP DI
POP CX
MOV AL,DH ;Store quotient.
STOSB
INC SI
LOOP NEXT_DIVIDE
MOV DI,OFFSET WORKSPACE + SIZE_DIVIDEND - 1
MOV CX,SIZE_MANTISSA
STD
CALL ROUND ;Round quotient.
JC DIVIDE_ERROR
MOV CX,SIZE_MANTISSA
MOV SI,OFFSET WORKSPACE + SIZE_DIVIDEND - 2
MOV DI,OPERAND ;Store quotient in operand.
ADD DI,CX
REP MOVSB
MOV AL,SAVE_SIGN ;Add the sign.
STOSB
MOV CX,SIZE DECIMAL ;Check for overflow.
NEXT_OVER2: LODSB
OR AL,AL
JNZ DIVIDE_ERROR
LOOP NEXT_OVER2
DIVIDE_END: POP SI
POPF
POP BP
RET
DIVIDE_ERROR: MOV DX,OFFSET OVERFLOW
POP SI
JMP ERROR_EXIT
;----------------------------------------------;
; INPUT: BX -> 1st and AH=sign; DI -> 2nd and AL=sign.
; OUTPUT: Result stored in DI.
MODULO: PUSH AX ;Save sign of dividend.
PUSH DI ;Divisor pointer.
ADD DI,SIZE INTEGER + 1
MOV CX,SIZE INTEGER
STD
CALL ROUND ;Round divisor.
JC MODULO_ERROR
POP DI
PUSH DI
CALL MAKE_INTEGER ;Truncate to integer.
MOV DI,BX
ADD DI,SIZE INTEGER + 1
MOV CX,SIZE INTEGER
CALL ROUND ;Round dividend.
JC MODULO_ERROR
MOV DI,BX
CALL MAKE_INTEGER ;Truncate to integer.
POP DI
MOV MODULO_FLAG,1 ;Do modulo division.
CLD
CALL DIVIDE
MOV MODULO_FLAG,0
MOV DI,OPERAND
POP AX ;Sign of dividend.
MOV AL,AH
STOSB ;Store it.
PUSH SI
MOV SI,OFFSET DIVIDEND ;Dividend has remainder.
MOV CX,SIZE_MANTISSA
REP MOVSB ;Store it in operand.
POP SI
RET
MODULO_ERROR: MOV DX,OFFSET OVERFLOW
JMP ERROR_EXIT
;----------------------------------------------;
; INPUT: DI -> Start of mantissa.
MAKE_INTEGER: PUSHF
CLD
ADD DI,SIZE INTEGER + 1
MOV CX,SIZE DECIMAL
XOR AL,AL
REP STOSB
POPF
RET
;----------------------------------------------;
; INPUT: DI -> End of mantissa + 1; Direction flag reversed.
; CX = number of bytes to round.
; OUTPUT: CY=1 if overflow.
ROUND: CMP BYTE PTR [DI],5 ; 5 or greater?
JB ROUND_DONE ;If no, done here.
DEC DI ;Else, move left one digit.
STC ;Start with carry.
NEXT_ROUND: MOV AL,[DI]
ADC AL,0 ;Add carry.
AAA ;ASCII adjust.
STOSB
JNC ROUND_END ;If no carry, done.
LOOP NEXT_ROUND
JMP SHORT ROUND_END
ROUND_DONE: CLC ;Return overflow.
ROUND_END: RET
;----------------------------------------------;
RESULT: MOV DX,OFFSET RESULT_MSG ;Display "Result is ".
CALL PRINT_STRING
MOV SI,OPERAND ;Point to result.
LODSB
OR AL,AL ;Print sign if negative.
JZ PRINT_INTEGER
CALL PRINT_CHAR
PRINT_INTEGER: XOR DH,DH ;Non-zero digit flag.
MOV BL,3 ;Comma every third digit.
MOV CX,INTEGER_PRECISION
JMP SHORT NEXT_INT2 ;Skip prefacing comma.
NEXT_INT: OR DH,DH ;Non-zero digit yet?
JZ NEXT_INT2 ;If no, skip.
MOV AX,CX ;Is position MOD 3 = 0?
DIV BL
OR AH,AH
JNZ NEXT_INT2 ;If no, no comma.
MOV AL,"," ;Else, delimiting comma.
CALL PRINT_CHAR
NEXT_INT2: LODSB
OR AL,AL ;Is digit zero?
JNZ PRINT_INT ;If no, print it.
OR DH,DH ;Else, non-zero digit yet?
JZ LOOP_INTEGER ;If no, skip.
PRINT_INT: OR DH,AL
CALL PRINT_NUMBER
LOOP_INTEGER: LOOP NEXT_INT
PUSH SI ;Save pointer.
MOV CX,DECIMAL_PRECISION ;Count the significant decimal
MOV BX,CX ; places.
INC BX
NEXT_FRACTION: LODSB
OR AL,AL
JZ LOOP_FRACTION
MOV BX,CX ;If non-zero, save count.
LOOP_FRACTION: LOOP NEXT_FRACTION
POP SI ;Restore pointer.
NEG BX ;Get count of significant.
ADD BX,DECIMAL_PRECISION + 1
MOV CX,DECIMAL_PLACES ;Use the larger of significant
CMP CX,BX ; digits; number entered or
JAE DO_FRACTION ; result.
MOV CX,BX
DO_FRACTION: JCXZ CK_NULL ;If no significant, done here.
OR DH,DH ;Else, was there a non-zero
JNZ PRINT_DECIMAL ; integer?
XOR AL,AL ;If no, preface with zero.
CALL PRINT_NUMBER
PRINT_DECIMAL: MOV AL,"." ;Display delimiting decimal.
CALL PRINT_CHAR
XOR BL,BL ;Comma counter.
NEXT_DEC: CMP BL,3
JNZ NEXT_DEC2
MOV AL,","
CALL PRINT_CHAR
XOR BL,BL
NEXT_DEC2: LODSB ;Display decimal digits.
CALL PRINT_NUMBER
INC BL
LOOP_DEC: LOOP NEXT_DEC
JMP SHORT RESULT_END
CK_NULL: OR DH,DH ;Was there a non-zero integer?
JNZ RESULT_END ;If yes, done here.
XOR AL,AL ;Else, display solitary, zero.
CALL PRINT_NUMBER
RESULT_END: MOV DX,OFFSET CRLF ;New line.
CALL PRINT_STRING
;----------------------------------------------;
SAVE_ANSWER: MOV SI,OPERAND ;Copy operand to answer.
MOV DI,OFFSET ANSWER
MOV CX,SIZE NUMBER
REP MOVSB
MOV AH,30H ;Get DOS version.
INT 21H
CMP AL,3 ;Filename in environment
JB TRY_CURRENT ; not support before DOS 3.x.
MOV DS,DS:[2CH] ;Segment of environment.
XOR SI,SI
FIND_ENV_END: LODSB ;Find end of environment.
OR AL,AL
JNZ FIND_ENV_END
LODSB
OR AL,AL
JNZ FIND_ENV_END
INC SI ;Bump pointer past word count.
INC SI
MOV DI,OFFSET WORKSPACE ;Copy our name.
CALL COPY_NAME
PUSH CS
POP DS
MOV DX,OFFSET WORKSPACE ;Try to write answer.
CALL WRITE_ANS
JNC ANSWER_END
TRY_CURRENT: MOV DX,OFFSET COMPUTE ;Try current directory.
CALL WRITE_ANS
JNC ANSWER_END
; See if PATH= is in environment.
MOV DS,DS:[2CH] ;Segment of environment.
LOOK_AT_PATH: MOV BX,-1 ;Offset zero in environment.
NEXT_PATH: INC BX
MOV SI,BX
CMP BYTE PTR [SI],0 ;Terminating null?
JNZ PATH_SEARCH
CMP BYTE PTR [SI+1],0 ;Double null?
JNZ NEXT_PATH
JMP SHORT CANT_SAVE ;If so, end of environment.
PATH_SEARCH: MOV DI,OFFSET PATH ;Search for "PATH=".
MOV CX,PATH_LEN
REP CMPSB
JNZ NEXT_PATH
; PATH= found; search for paths.
NEXT_STRING: CMP BYTE PTR [SI],0 ;Terminating null?
JZ CANT_SAVE
CMP BYTE PTR [SI-1],0 ;Double null?
JZ CANT_SAVE
MOVE_STRING: MOV DI,OFFSET WORKSPACE ;Get path.
NEXT_BYTE: LODSB
OR AL,AL
JZ SEARCH_PATH
CMP AL,";"
JZ SEARCH_PATH
STOSB
JMP NEXT_BYTE
SEARCH_PATH: CALL COPY_PATH ;Create filespec.
PUSH DS
PUSH CS
POP DS
MOV DX,OFFSET WORKSPACE
CALL WRITE_ANS ;Try to write answer.
POP DS
JC NEXT_STRING ;If failed, try next path.
JMP SHORT ANSWER_END ;Else, done.
CANT_SAVE: PUSH CS
POP DS
MOV DX,OFFSET NOT_FOUND ;Answer write error message.
CALL PRINT_STRING
ANSWER_END: RET
;----------------------------------------------;
COPY_NAME: LODSB
STOSB
OR AL,AL
JNZ COPY_NAME
RET
;----------------------------------------------;
COPY_PATH: PUSH SI
PUSH DS
CMP DI,OFFSET WORKSPACE
JZ MOVE_NAME
CMP BYTE PTR ES:[DI-1],"\" ;Filespec delimiter?
JZ MOVE_NAME
CMP BYTE PTR ES:[DI-1],":"
JZ MOVE_NAME
MOV AL,"\" ;If no, add one.
STOSB
MOVE_NAME: PUSH CS
POP DS
MOV SI,OFFSET COMPUTE ;Tack on "COMPUTE.COM".
MOV CX,COMPUTE_LEN
REP MOVSB
COPY_END: POP DS
POP SI
RET
;----------------------------------------------;
; INPUT: DX -> Compute.com filespec.
; OUTPUT: CY=0 if successful; CY=1 if failed.
WRITE_ANS: PUSH SI
MOV AX,3D02H ;Open for reading and writing.
INT 21H
JC WRITE_END
MOV BX,AX
MOV DX,OFFSET WORKSPACE
MOV CX,SIZE WORKSPACE
MOV AH,3FH ;Read signature.
INT 21H
JC WRITE_END
MOV SI,100H ;Make sure we found correct
MOV DI,OFFSET WORKSPACE ; Compute.com.
REP CMPSB
STC
JNZ WRITE_END
XOR CX,CX
MOV DX,OFFSET ANSWER - 100H
MOV AX,4200H ;Seek offset of answer.
INT 21H
JC WRITE_END
MOV DX,OFFSET ANSWER
MOV CX,SIZE ANSWER
MOV AH,40H ;Write answer to disk.
INT 21H
JC WRITE_END
MOV AH,3EH ;Close file.
INT 21H
WRITE_END: POP SI
RET
;----------------------------------------------;
PRINT_NUMBER: ADD AL,"0" ;Binary to ASCII.
PRINT_CHAR: MOV DL,AL
MOV AH,2 ;Print char via DOS.
INT 21H
RET
;----------------------------------------------;
PRINT_STRING: MOV AH,9 ;Print string via DOS.
INT 21H
RET
;----------------------------------------------;
; HEAP
INITIAL_VALUE NUMBER <>
_TEXT ENDS
END START