数字计算器的汇编语言实现.docx
《数字计算器的汇编语言实现.docx》由会员分享,可在线阅读,更多相关《数字计算器的汇编语言实现.docx(23页珍藏版)》请在冰豆网上搜索。
数字计算器的汇编语言实现
数字计算器的汇编语言实现
1项目特点及目的
本课程设计是一次程序设计方法及技能的基本训练,通过实际程序的开发及调试,巩固课堂上学到的关于程序设计的基本知识和基本方法,进一步熟悉汇编语言的结构特点和使用,达到能独立阅读、设计编写和调试具有一定规模的汇编程序的水平。
2题目简介
用8086汇编语言编写一个能实现四则混合运算、带括号功能的整数计算器程序。
程序能实现键盘十进制运算表达式的输入和显示(例如输入:
“1+2*(3-4)”),按“=”后输出十进制表示的运算结果。
3程序设计要求
◆遵循模块化、结构化的编程思路
◆程序必须正确运行
◆程序简明易懂,多标明注释,具有良好的程序书写风格
◆适当优化程序,提高程序的运行效率
4工作条件
使用的设备及软件为8086兼容机及MASM汇编开发软件。
5题目分析
根据题目要求,可以把程序的工作过程划分为运算表达式输入、计算、结果输出三部分。
因此在编写程序时可以按此把程序大致划分为三个模块。
5.1运算表达式输入
用户通过键盘输入的运算表达式为一个ASCII码字符串,字符串的最后一个字符是“=”号。
对于这个运算表达式,“+、-、*、/、(、)、0~9、=”是合法的表达式内容,其他的字符则是无法进行运算的非法内容,因此需要首先进行表达式合法性检查。
另外,由于计算机能进行计算的是2进制的补码,因此还需要把以ASCII码表示的数值转换为补码的形式并加以保存。
当然,控制运算方式的符号也要进行保存。
因此,“运算表达式输入”这个模块可以细化为:
表达式合法性检查、数值的ASCII码到补码转换及保存、符号的保存三个小部分,如图6-1所示。
图6-1“运算表达式输入”的流程图
5.1.1运算表达式合法性的检查方法
观察“ASCII字符编码表”,可以发现“+、-、*、/、(、)”的ASCII码由28H到2FH,而“0~9”的ASCII码则由30H到39H,因此只需对输入的字符一个一个地进行数值范围比较,看看是否处于28H~39H这个范围里面,即可区分输入的表达式是否合法,流程图如图6-2所示。
此流程图是采用循环输入字符的方法,每输入一个字符即进行判断。
读者也可以采用输入字符串的方法,把整个运算表达式接收完毕后再进行判断。
图6-2运算表达式合法性检查流程图一
另一方面,对于含有括号的运算表达式,当左括号的数量与右括号数量不相等时,表达式也是非法的。
因此,可以设置一个起始值为0的变量(下面称其为配对标志),当输入“(”时此变量加一,当输入“)”时减一,则当表达式输入结束时,只需判定此配对标志是否为0,即可判定左右括号数量是否相等。
图6-3运算表达式合法性检查流程图二
5.1.2数值的补码转换方法
要进行数值的ASCII码到补码的转换,首先就得判断输入的字符是数值还是符号。
根据上文所提,“+、-、*、/、(、)”的ASCII码由28H到2FH,而“0~9”的ASCII码则由30H到39H,只需比较字符是否小于等于2FH(或小于30H)即可判断是否为符号,否则则是数值,如图6-2所示。
众所周知,要把一个ASCII码数值转换为二进制补码的形式,只需要对其减30H即可实现。
但如果输入的是多位数,例如123,那么计算机获得的是31H、32H、33H三个字节,即使分别对这三个字节进行减30H操作,也只是获得1、2、3三个数而已。
实际上可以利用加权的方法合并这几个数:
123=1×100+2×10+3×1
但另一个问题是,由于输入是随机的,即输入的运算数有多少位是未知的,因此无法使用上面的方面静态确定每一位的权重。
这里介绍的方法是,每输入运算数的一位,则把前面的合并结果(称为原值)乘以10再与这一位相加,实现动态的加权合并。
例如:
令原值为0,输入1,结果为:
0×10+1=1
输入2,结果为:
1×10+2=12
输入3,结果为:
12×10+3=123
即:
123=(((0×10+1)×10+2)×10)+3。
数值的补码转换流程如图6-4所示,当然,在获得第一个数值输入前要先把原值设置为0。
图6-4数值的补码转换流程图
由于符号全部是一个字节,无需进行任何转换即可保存,处理简单,这里不作探讨。
5.2计算
由于运算表达式有多个数值和符号,而符号有不同的优先级别,因此上文提到的数值保存和符号保存应该分开两个地方进行保存,这样有利于表达式的计算算法设计。
下面把“+、-、*、/”称为运算符,把“(、)”称为优先符。
图6-5运算表达式的存储举例
观察图6-5的三条运算表达式,再联系四则混合运算的优先原则,可以归纳出几点:
(a)数值的数量是运算符的数量加1(优先符不算),第1个运算符代表第1、2个数值的运算操作,第N个运算符代表第N、N+1个数值的运算操作……
(b)每进行一次运算,相应的运算符即被消除,而参与运算的两个数值合并为一个数值,仍然满足(a)。
例如图6-5(a),当完成乘法运算后,数值存储区有408、56两个数,符号存储区有“+”一个运算符。
(c)括号(优先符)的作用是把括号内的运算符的优先级别提高到比外部高。
因此,要实现运算表达式的运算,最重要的就是确定所有运算符的优先级别。
下面讨论运算符优先级别的编程设计方法。
5.2.1运算优先级别的静态确定法
此方法是完成了把整条运算表达式全部存入数值存储区和符号存储区后才开始对运算符优先级进行判断的方法:
⏹设置“*、/”的优先级为2、“+、-”的优先级为1;
⏹括号内部的所有运算符的优先级全部加2。
运用优先级别静态确定法处理图6-5的三条表达式的运算符,结果如图6-6所示。
其中图6-6(c)的“34+56-8”由于被括号括起两次,因此其两个运算符“+、-”的优先级别均加了两次2。
图6-6运算符的静态优先级别
最后,由于四则混合运算遵循从左往右计算的原则,即相同优先级别的运算符靠左的优先。
因此,只需计算出符号存储区里面的所有运算符的优先级别,然后根据优先级的大小先后执行运算符对应的运算即可实现计算(当然每进行一次运算,相应的运算符即被消除,而参与运算的两个数值合并为一个数值)。
当数值存储区里面剩下一个数值时,运算结束,这个最后的数值就是运算的最终结果。
读者请自行设计此算法的流程图。
5.2.2运算优先级别的动态确定法
运算优先级别静态确定法具有容易理解、实现简单的优点,而其缺点是:
如果运算表达式太长、太多数值和符号时,则会占用较多的存储空间,而且计算优先级的工作量也会增多。
动态确定法是在运算表达式未结束输入即开始计算的一种方法。
由于在表达式输入阶段已开始计算,因此计算结果的速度比静态确定法快。
观察图6-5(a),当用户输入“+”时,已经可以开始计算“12*34”;观察图6-5(b),当用户输入“-”时,已经可以开始计算“34+56”;观察图6-5(c),当用户输入第一个“-”时,已经可以开始计算“12*21”。
也就是说,当用户输入的运算符的优先级不大于前一个运算符时,即可开始前一个运算符的计算。
问题是,对于有括号的运算表达式,在用户没有完成运算表达式的全部输入前,很难提前确定括号内部运算符的优先级。
为了解决这个问题,动态确定法把优先符(括号)也赋予了优先级:
⏹“(”,优先级为5;
⏹“*、/”,优先级为4;
⏹“+、-”,优先级为3;
⏹“)”,优先级为1。
计算图6-5三条运算表达式的所有符号的优先级别,结果如图6-7所示。
图6-7运算符的动态优先级别
设计计算的条件:
(1)只有优先级为3、4的符号(即+、-、*、/)可以进行计算;
(2)如果某符号的优先级大于等于下一个的优先级时,对此符号进行相应运算(当然每进行一次运算,相应的运算符即被消除,而参与运算的两个数值合并为一个数值);
(3)如果左右括号相邻,且左括号在右括号左边时(即在符号存储区里面出现“()”的情况,或者在优先级队列里出现“51”的情况),把这对括号消除掉。
最后,当数值存储区里面剩下一个数值(或者符号存储区里面没有符号)时,运算结束,这个最后的数值就是运算的最终结果。
读者请自行设计此算法的流程图。
5.3结果输出
当数值存储区里面剩下一个数值(或者符号存储区里面没有符号)时,运算结束,需要把运算结果输出显示。
分析运算结果的特点:
运算结果为一个2进制补码,整数,如果数据长度为16位,则运算结果范围是:
-32768~32767。
运算结果的输出要解决的主要问题是:
正负数区分、补码到ASCII码转换并输出显示。
运算结果的输出流程如图6-8所示。
图6-8结果输出流程图
5.3.1正负数区分
运算结果有三种情况:
正整数、负整数、零。
运算结果以补码形式对这三种情况进行统一的存储,但显示输出时则有所不同。
负整数前面需要显示“-”号,因此需要对运算结果的符号进行判断。
另一方面,正整数和零的补码与原码相同,而负整数的补码则不一样。
把负整数进行取补码运算,把它转换为原码,可以实现运算结果统一的ASCII码转换输出方法,而不需要分别为正整数和零、负整数分别设计两个不同的ASCII码转换程序,如图6-9所示。
图6-9正负数区分流程图
5.3.2补码到ASCII码转换
计算结果在屏幕上的输出显示实际上是ASCII码的输出显示。
假设程序采用的数据长度为16位,则运算结果范围是:
-32768~32767,即屏幕最多得显示5位ASCII码。
由于上文已经把结果统一为原码,下面介绍如何把原码转换为ASCII码。
这个转换过程实际上跟上文的“数值的补码转换方法”是相反操作。
例如要把123在屏幕上输出显示,即要把123的百位、十位、个位分离,得到1、2、3,然后转换为31H、32H、33H三个ASCII码。
众所周知,把一位数转换为ASCII码只需加30H即可,下面介绍把一个多位数的各位分离的方法。
(一)除十法
分离方法是:
对一个多位数进行除10处理,得到的余数即为个位数,而商则是删除个位后的多位数。
对商反复进行除10处理,直到商为0为止,即可把各位数分离。
例如对123进行除十法处理:
123/10,商是12,余数是3
12/10,商是1,余数是2
1/10,商是0,余数是1
可见经过三次除十计算,得到的三个余数刚好就是对123的各位的分离结果。
接着只需分别对这些余数加30H即可转换为ASCII码,实现输出转换。
除十法的优点是不需要理会要输出的数值有多少位,不断除以10直到商为0即可;缺点是得到的余数的顺序跟输出的方向相反,不方便输出。
例如上例得到的三个余数的顺序是3、2、1,加30H转换输出后屏幕显示为“321”,跟期望显示的顺序相反,要作进一步处理。
处理方法是把余数放进堆栈里面,然后再出栈显示。
由于堆栈是先进后出的,即可解决该输出的顺序问题。
图6-10除十法流程图
(二)除最高位法
分离方法是先除以10位数-1,得到的商即为最高位,余数为删除最高位后的多位数。
接着令余数除以10位数-2,得到的商为次高位,……。
例如123,其位数是3(个位、十位、百位),则计算过程为:
123/103-1,商是1,余数是23
23/103-2,商是2,余数是3
3/103-3,商是3,余数是0
可见经过三次计算,得到的三个商刚好是对123的各位的分离结果,而且顺序跟输出方向相同。
可以直接加30H转换输出,屏幕显示为“123”。
该方法的缺点是:
必须首先确定要输出的数值有多少位,编程者必须十分清楚需要输出的数值的数值范围。
图6-11除最高位法流程图
源程序代码(与上面讲解的方法有点出入)
;------数据段-------------
DATASEGMENT
TAB1DB13,10,"HUIBIANJISUANQISHEJI$"
TAB2DB13,10,"1234$"
TAB3DB13,10,"5678$"
TAB4DB13,10,"90+-$"
TAB5DB13,10,"*/()$"
TAB6DB13,10,"PLEASEINPUTAFORMULA:
$"
TAB7DB13,10,"$"
NUM1DW100DUP(?
)
NUM3DW100DUP(?
)
NUM4DW100DUP(?
)
C0DW0
C1DW0
C2DW0
SUM1DW0
SUM2DW0
OLTDW0
RESULTDW0
TABLEDW10000,1000,100,10,1;定义输出时要用到的除数
WRONGDB13,10,"WRONGINPUT!
$"
;OVERDB13,10,"NOTOPERATIONALCHARACTER!
$"
DATAENDS
;----------------------------
;------代码段----------------
CODESEGMENT
ASSUMECS:
CODE,DS:
DATA
;------主程序---------------
START:
MOVAX,DATA
MOVDS,AX
CALLDISPMENU;显示汇编计算器菜单
STARTA:
MOVBX,-1
MOVSI,0
MOVDI,30
MOVCX,0
MOVC0,0
MOVC1,0
MOVC2,0;初始化各个变量
CALLINPUT
CALLYUNSUAN
CALLOUTPUT
EXIT:
MOVAH,4CH
INT21H
;------计算器显示菜单子程序--------
DISPMENUPROCNEAR
MOVDX,OFFSETTAB1
MOVAH,09H
INT21H
MOVDX,OFFSETTAB2
MOVAH,09H
INT21H
MOVDX,OFFSETTAB3
MOVAH,09H
INT21H
MOVDX,OFFSETTAB4
MOVAH,09H
INT21H
MOVDX,OFFSETTAB5
MOVAH,09H
INT21H
MOVDX,OFFSETTAB6
MOVAH,09H
INT21H
MOVDX,OFFSETTAB7
MOVAH,09H
INT21H
RET
DISPMENUENDP
;---------------------------
;输入字符及转换程序模块
;----输入并转换子程序,结果式存入NUM1-------
INPUTPROCNEAR
STARTW:
MOVAH,1
INT21H
MOVAH,0
CMPAL,1BH;'ESC'
JZEXIT
CMPAL,2AH;'*'
JZMARK
CMPAL,2FH;'/'
JZMARK
CMPAL,2BH;'+'
JZMARK
CMPAL,2DH;'-'
JZMARK
CMPAL,29H;')'
JZMARK
CMPAL,28H;'('
JZMARK
CMPAL,3DH;'='
JZEQUAL
SUBAL,30H
CMPAL,0
JLERROR
CMPAL,9
JAERROR
JMPARITH
ERROR:
;显示输入错误
LEADX,WRONG
MOVAH,9
INT21H
JMPSTARTA
ARITH:
CMPC1,0
JETTOM
MANA:
;数值的补码转换
MOVC0,AX
ADDC1,1
MOVAX,BX
MOVCX,10
MULCX
ADDAX,C0
MOVBX,AX
JMPSTARTW
TTOM:
MOVBX,0
JMPMANA
MARK:
CMPBX,-1
JEGGON
MOVNUM1[SI],BX
ADDSI,2
MOVNUM1[SI],AX
MOVBX,-1
MOVC1,0
ADDSI,2
JMPSTARTW
GGON:
MOVNUM1[SI],AX
ADDSI,2
JMPSTARTW
EQUAL:
;输入是等号时,调到主程序执行运算
CMPBX,-1
JELLOP
MOVNUM1[SI],BX
ADDSI,2
MOVNUM1[SI],AX
JMPENDW
LLOP:
MOVNUM1[SI],AX
JMPENDW
ENDW:
RET
INPUTENDP
;---------------------------
;-----运算的子程序----------
;使括号内的运算先进行,别用算出的结果代替对应括号及括号内的运算的子程序
YUNSUANPROCNEAR
AAAA:
MOVBP,0
MOVDI,0
MOVBX,0
MOVC0,0
MOVC1,0
MOVSUM2,0
MOVSI,0
BBBB:
MOVAX,NUM1[SI]
CMPAX,29H;)先查找最左边的右括号
JERSEE
ADDSI,2
CMPAX,3DH;=
JELASTL
JMPBBBB
RSEE:
SUBSI,2
MOVAX,NUM1[SI]
CMPAX,28H;(
JEMOTT
JMPRSEE
MOTT:
MOVSUM2,SI
MOVDI,0
KKTY:
ADDSI,2
MOVAX,NUM1[SI]
MOVNUM3[DI],AX;把括号里的计算式存入NUM3
CMPAX,29H;)
JERCOUNT
ADDDI,2
JMPKKTY
RCOUNT:
MOVSUM1,SI
CALLCALC
MOVDI,SUM2
MOVAX,RESULT
MOVNUM1[DI],AX
MOVSI,SUM2
CCOO:
ADDSI,2
ADDDI,2
MOVAX,NUM1[SI]
MOVNUM1[DI],AX
CMPAX,3DH;=
JEAAAA
JMPCCOO
LASTL:
MOVSI,0
UUCC:
MOVAX,NUM1[SI]
MOVNUM3[SI],AX
ADDSI,2
CMPAX,3DH;=
JEENDL
JMPUUCC
ENDL:
CALLCALC
RET
YUNSUANENDP
;---------------------------------
;------计算混合运算的子程序(不含括号的)------------------
CALCPROCNEAR
MOVAX,NUM3
MOVNUM4,AX
MOVDI,2
MOVC0,SI
MOVSI,2
INT1:
MOVBX,NUM3[SI]
CMPBL,2AH;'*'
JEMULL
CMPBL,2FH;'/'
JEDIVV
CMPBL,3DH;'='
JECHUBU
CMPBL,29H;')'
JECHUBU
CMPBL,2BH;'+'
JEPOTT
CMPBL,2DH;'-'
JEPOTT
INT2:
MOVNUM4[DI],BX
ADDDI,2
ADDSI,2
MOVAX,NUM3[SI]
MOVNUM4[DI],AX
ADDDI,2
ADDSI,2
JMPINT1
POTT:
SUBDI,2
MOVNUM4[DI],AX
ADDDI,2
JMPINT2
MULL:
ADDSI,2
MOVBX,NUM3[SI]
MULBX
ADDSI,2
JMPINT1
DIVV:
ADDSI,2
MOVBX,NUM3[SI]
DIVBX
ADDSI,2
JMPINT1
ADDD:
ADDDI,2
MOVBX,NUM4[DI]
ADDAX,BX
ADDDI,2
JMPINT3
SUBB:
ADDDI,2
MOVBX,NUM4[DI]
SUBAX,BX
ADDDI,2
JMPINT3
CHUBU:
MOVNUM4[DI],BX
SUBDI,2
MOVNUM4[DI],AX
MOVAX,NUM4
MOVDI,2
INT3:
MOVBX,NUM4[DI]
CMPBX,2BH;'+'
JEADDD
CMPBX,2DH;'-'
JESUBB
MOVRESULT,AX
RET
CALCENDP
;---------------------------------
;-----输出最终结果的子程序---------------
OUTPUTPROCNEAR
LEASI,TABLE
MOVC0,SI
ADDC0,8
MOVCX,5
MOVAX,RESULT
MOVOLT,0
OUTT:
XORDX,DX;异或清零
DIVWORDPTR[SI];求万位,千位,百位,十位,个位;商存在了AX中,余数存在了DX中
ORAL,30H;转换成ASCLL码
MOVBX,DX;将DX的值存入BX中,因为下面输出AX值时会改变DX值
CMPSI,C0
JEOUT2
CMPOLT,0
JNEOUT2
CMPAX,30H
JEOUT1
JNEAADD
OUT2:
MOVDX,AX;输出商值
MOVAH,02
INT21H
OUT1:
MOVAX,BX;将余数赋还给AX因为除数十16位,所以余数在DX中
ADDSI,2;BP加二,用以调用下一个被除数
LOOPOUTT
CALLENTER
JMPSTARTA;跳回程序开始
RET
AADD:
INCOLT
JMPOUT2
OUTPUTENDP
;---------------------------------
;---------回车换行的子程序---------------------
ENTERPROCNEAR
MOVDL,0DH;回车
MOVAH,02H
INT21H
MOVDL,0AH;换行
MOVAH,02H
INT21H
RET
ENTERENDP
CODE