assume cs:code
data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995' ;年份
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000 ;总收入
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800 ;雇员人数
data ends
table1 segment
db 21 dup ('year summ ne ?? ') ;预备21个16位段;预设空格;给整理后的数据。
table1 ends
table2 segment
db 1024 dup (0)
table2 ends ;数据对应ASCII码预留
stack segment
dw 32 dup (0)
stack ends ;设置栈段
code segment
start:mov ax,stack
mov ss,ax
mov sp,64 ;ss:sp为栈段
mov ax,table1
mov ds,ax
mov bp,0 ;ds:bp指向整理后字符串首地址
mov ax,data
mov es,ax
mov bx,0 ;es:bx指向需整理数据的首地址;同时是年份和总收入变量
mov si,0 ;雇员人数变量初始化
mov cx,21 ;有21组数据,设21个循环
z0:push cx ;21组数据循环入栈
mov cx,4 ;年份数字4循环
mov di,0 ;年份列变量
z1:mov al,es:[bx][di]
mov ds:[bp][di],al
inc di ;将年份从data段移入table段
loop z1
mov ax,es:84[bx]
mov dx,es:86[bx]
mov ds:5[bp],ax
mov ds:7[bp],dx ;将总收入从data段移入table段
mov cx,es:168[si]
mov ds:10[bp],cx ;将雇员人数从data段移入table段
div cx
mov ds:13[bp],ax ;总收入/雇员人数,商放入table段
add bx,4 ;年份和总收入变量bx+4
add bp,16 ;table段中完成一行数据整理,行变量bp指向下一行
add si,2 ;雇员人数变量+2
pop cx ;21组数据循环参数出栈
loop z0
mov ax,table2
mov ds,ax
mov si,0 ;ds:si作为数据转换成ASCII码字符串后的存放地址,同时si也作为字符的列变量
mov ax,table1
mov es,ax
mov bx,0 ;es:bx指向整理好后数据(bx为行变量)
mov dh,2 ;(dh)=字符显示行号(取值范围0~24)
mov cx,21 ;21组数据及字符串循环
g0:push cx
mov dl,10 ;(dl)=字符显示列号(取值范围0~80);此处设置其在行内的初始位置
mov di,0 ;年份从table1段移入table2段的列变量
mov cx,4
g1:mov al,es:[bx][di]
mov ds:[si],al
inc di
inc si
loop g1 ;将年份从table1段移入table2段
push si ;将si的偏移量入栈,在处理年份后的第一组数据时恢复
sub si,4 ;将si指向年份的第一个字符
mov cl,01000010b
call show_str ;在屏幕上显示年份
pop si ;恢复si
inc si ;si+1。
push dx ;因随后的除法要用到dx,所以dx先入栈,以保护dh值
mov ax,es:5[bx]
mov dx,es:7[bx]
call dtoc ;总收入数据变为ASCII码,移入table2段
pop dx
mov dl,18 ;总收入显示的列地址
mov cl,01000010b
call show_str ;在屏幕上显示总收入
call w0 ;调用w0子程序,其作用是找到字符串结束的偏移地址,并将si指向下一个字符串的首地址
push dx
mov ax,es:10[bx]
mov dx,0
call dtoc
pop dx
mov dl,29 ;雇员人数显示的列地址
mov cl,01000010b
call show_str ;在屏幕上显示雇员人数
call w0 ;调用w0子程序
push dx
mov ax,es:13[bx]
mov dx,0
call dtoc
pop dx
mov dl,40 ;人均收入显示的列地址
mov cl,01000010b
call show_str ;在屏幕上显示人均收入
call w0 ;调用w0子程序
inc dh ;屏幕显示指向下一行
add bx,16 ;table1段指向下一行数据
pop cx
loop g0
mov ax,4c00h
int 21h
w0:mov cl,ds:[si] ;字符串首地址内的内容放入cl
mov ch,0
jcxz c0 ;判断cx是否为0(字符是否是“.”,即字符串是否结束),如不是si+1,判断下一个字符;如是,si+1(使table2段中字符串分开),返回主程序
inc si
jmp short w0
c0:inc si
ret
dtoc:push bx
push cx
push dx
push ax
push si
push bp ;子程序中会使用的寄存器入栈
mov bp,0 ;余数计算器初始化
d0:mov cx,10 ;除10取余法,将10放入cx
call divdw ;调用除法溢出子程序
push cx ;将余数依次入栈
inc bp ;纪录余数的数量
mov cx,dx ;商高位存入cx
jcxz d1 ;判断商高位是否为0,如是0跳到d1处执行
jmp short d0 ;跳到d0处执行
d1:mov cx,ax ;商低位存入cx
jcxz d2 ;判断商低位是否为0,如是0跳到d2处执行
jmp short d0 ;跳到d0处执行
d2:mov cx,bp ;将余数的数量存入cx
d3:pop bx ;倒序输出余数
add bx,30h ;将十进制数码转换成ASCII码
mov [si],bl ;将余数以字节方式输入ds:si。
inc si
loop d3
mov bx,0
mov [si],bx ;设置ASCII码结束符,显示字符串子程序根据此符号判断字符串是否结束
pop bp
pop si
pop ax
pop dx
pop cx
pop bx ;子程序中会使用的寄存器出栈
ret ;ip跳到call指令的下一条指令执行
divdw:push bx
push bp ;将子程序中使用的寄存器入栈
mov bp,ax ;将L存入bp中
mov ax,dx ;将H放入ax中
mov dx,0 ;dx置0(即将H变为dword型)
div cx ;H/N(商在ax中,余数在dx中)
mov bx,ax ;将H/N的商放入bx中
mov ax,bp ;L存入ax中
div cx ;[rem(H/N)*65536+L]/N;L值在ax中,rem(H/N)值为H/N的余数,在dx中
mov cx,dx ;将[rem(H/N)*65536+L]/N的余数放入cx中
mov dx,bx ;将H/N的商放入高16位的dx中;结果的低16位([rem(H/N)*65536+L]/N的商)仍然在ax中
pop bp
pop bx ;子程序中使用的寄存器出栈
ret
show_str:push es
push dx
push cx
push bx
push ax
push si ;子程序中会使用的寄存器入栈
mov al,dh
mov ah,0ah
mul ah ;第N行的段地址偏移量(相对于第0行的基础段地址)
add ax,0b800h ;第N行的段地址存入ax
mov es,ax ;屏幕上行号的段地址存入es
mov ah,cl ;将颜色存入ah,因后面要用到cx
mov bh,0
mov bl,dl
add bl,bl
sub bx,2 ;将列号存入bx,因内存寻址变量只能选bx、bp、si、di四个
s0:mov cl,[si]
mov ch,0
jcxz ok ;字符串中出现0,字符处理完毕,跳到ok处
mov al,[si] ;将字符存入al
mov es:[bx],ax ;将带属性的字符在屏幕上显示
inc si ;字符串列变量加1,指向下个字符
add bx,2 ;屏幕列变量指向下一个字节
jmp short s0 ;ip跳到s0处执行指令
ok:pop si
pop ax
pop bx
pop cx
pop dx
pop es ;子程序中会使用的寄存器出栈
ret ;ip跳到call指令的下一条指令执行
code ends
end start