任务
  将实验7中的Power idea公司的数据按照图10.2所示的格式在屏幕上显示出来。

assume cs:code,ds:data

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,25257,17800
data ends

data_str segment
    db 16 dup (0)
data_str ends

code segment
start:
    call clear_screen
start_i:
    mov dh,0
    mov cx,21
    print_row_start:
        call print_row
        inc dh
    loop print_row_start

loop start_i
    mov ax,4c00h
    int 21h

; 名称: print_row
; 功能: 打印一行记录
; 参数: (dh)=行号
print_row:
    push es
    push bx
    push ds
    push si
    push dx
    push cx

    mov bx,data
    mov es,bx

    mov bx,data_str
    mov ds,bx

    print_row_year:
        ; 计算年份偏移地址
        mov ax,4
        mul dh

        ; ds:si 指向保存字符串的地址
        mov si,0

        ; 把原数据复制到ds:si,一次操作2字
        mov bx, ax
        mov ax, es:[bx]
        mov ds:[si],ax
        mov ax, es:[bx][2]
        mov ds:[si][2],ax
        mov ds:[si][4],0

        ; 打印字符
        mov dl,1
        mov cl,31
        call show_str

    print_row_amount:
        ; 计算收入偏移地址
        mov ax,4
        mul dh
        ; ds:si 指向保存字符串的地址
        mov si,0

        push ax ; 收入地址入栈,用于后面计算人均收入

        push dx
        mov bx, ax
        mov ax, es:84[bx]
        mov dx, es:84[bx][2]
        call dtoc
        pop dx

        ; 打印字符
        mov dl,11
        mov cl,31
        call show_str

    print_row_count:
        ; 计算人员偏移地址
        mov ax,2
        mul dh
        ; ds:si 指向保存字符串的地址
        mov si,0

        push ax ; 人员地址入栈,用于后面计算人均收入

        push dx
        mov bx, ax
        mov ax, es:168[bx]
        mov dx, 0
        call dtoc
        pop dx

        ; 打印字符
        mov dl,25
        mov cl,31
        call show_str

    print_row_avg:
        ; 计算人均收入
        pop bx
        mov cx, es:168[bx]  ; 人员数量
        
        pop bx
        push dx
        mov ax, es:84[bx]   ; 总收入
        mov dx, es:84[bx][2]
        call divdw
        call dtoc
        pop dx

        ; 打印字符
        mov dl,35
        mov cl,31
        call show_str

    pop cx
    pop dx
    pop si
    pop ds
    pop bx
    pop es
    ret


; 名称: dtoc
; 功能: 将dword型数转变为表示十进制数的字符串,字符串以0为结尾符
; 参数: (ax)=dword型数据的低16位
;       (dx)=dword型数据的高16位
;       ds:si指向字符串的首地址
; 返回: 无
dtoc:
    push dx
    push cx
    push ax
    push bx

    mov bx,0
    push bx  ; push进栈一个0值,用于在pop的时候判断是否已经到字符串尾部
    dtoc_div_start:
        mov cx,10
        call divdw

        add cx,30H  ; cx是余数,入栈
        push cx

        mov cx,dx   ; 判断商的dx和ax是否都为0,如果是则结束
        jcxz judge_ax
        loop dtoc_div_start
        judge_ax:
        mov cx,ax   ; 把ax放入cx,用于判断商为0时结束循环
        inc cx
        loop dtoc_div_start

    dtoc_stack_to_data:
        pop dx
        mov byte ptr ds:[si][bx],dl
        inc bx

        mov cx,dx   ; 把dx放入cx,用于判断dx为0时结束循环
        inc cx
        loop dtoc_stack_to_data

    pop bx
    pop ax
    pop cx
    pop dx
    ret

; 名称:divdw
; 功能:进行不会溢出的除法运算,被除数为dword型,除数为word型,结果为dword型
; 参数:(ax)=dword型数据的低16位
;       (dx)=dword型数据的高16位
;       (cx)=除数
; 返回:(dx)=结果的高16位,(ax)=结果的低16位
;       (cx)=余数
; 公式:X/N = int(H/N)*65536 + (rem(H/N)*65536+L)/N
divdw:
    push bx
    push ax     ; 把ax中的被除数低位暂存 L

    mov ax,dx   ; 把dx放入低ax位计算 H/N
    mov dx,0
    div cx      ; H/N 商在AX,余数在DX

    mov bx,ax   ; 暂存 H/N 商

    ; mov dx, dx ; 因为 rem(H/N)*65536 = rem(H/N)*10000H 即左移4位,所以上次余数在DX中不用动
    pop ax  ; 取出被除数低位 L
    div cx  ; 商在AX,余数在DX

    ; mov ax,ax ; 低位的商在ax中不变
    mov cx,dx   ; dx中的余数传送到cx中
    mov dx,bx   ; 高位计算的商传送到dx中

    pop bx
    ret
; divdw

; 名称:show_str
; 功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
; 参数:(dh)=行号(取值范围0~24), (dl)=列号(取值范围0~79),
;      (cl)=颜色,ds:si 指向字符串的首地址
; 返回:无
show_str:
    push ax
    push dx
    push cx
    push bx

    ; 计算指定行号的偏移地址
    mov al,160
    mov ah,0
    mul dh
    mov bx,ax

    ; 计算列号偏移地址
    mov al,2
    mov ah,0
    mul dl
    
    ; 把列号偏移地址加到行号偏移地址中,保存在bx
    add bx,ax  

    ; 打印字符串
    push es
    push di
    push si
    print_str:

        ; 把显存地址B800段地址放入ES段寄存器
        mov ax,0B800H
        mov es,ax
        ; di作为显存地址的字符列偏移地址,每个字符占2字节
        mov di,0

        ; 先把cl中的颜色保存到al中,因为后面会用到cx做为跳转判断字符串是否结束 
        mov al,cl
        print_str_put:
            mov ch,[si] ; 把字符串放入ch,并把cl置为0
            mov cl,0
            jcxz ok ; 判断cx中的字符串是否为0,如果是说明字符串已结束,跳转到OK处
            
            mov byte ptr es:[bx][di],ch
            mov byte ptr es:[bx][di][1], al
            add si,1    ; si作为字符串偏移地址
            add di,2    ; di作为显存偏移地址
        loop print_str_put

    ok:
    pop si
    pop di
    pop es

    pop bx
    pop cx
    pop dx
    pop ax
    ret
; show_str 

clear_screen:
    push bx 
    push ds

    mov ax,0b800h
    mov ds,ax
    
    mov bx,0h
    mov cx,2000
    clear_screen_bx:
        mov word ptr ds:[bx][si],0h
        add bx,2
    loop clear_screen_bx

    pop ds
    pop bx 
    ret
; clear_screen

code ends

end start