8086 汇编从翘课到精通

发布时间 2023-11-10 19:11:33作者: Rainycolor

写在前面

编译器为 MASM-v6.11

写的一坨屎。

斐波那契数列前 50 项

最多支持输出 30 位十进制数。

.model large

assume cs:code, ss:stack

position segment; 在屏幕上的输出位置
	dw 00a0h
position ends

string segment ;将数组转化为字符串输出
  db 30 dup (0), 0
string ends

data segment ;三个用于递推 fib 的数组
	a1 db 1, 29 dup (0)
	a2 db 1, 29 dup (0)
	a3 db 1, 29 dup (0)
data ends

stack segment ;栈
	dw 40H dup(0)
stack ends

code segment
main:
	call init ;初始化栈
	

	call transforming ;首先输出前两项
	call show_string
	call transforming
	call show_string
	mov cx, 50 - 2 ;输出之后的 48 项(理论上可以正确输出无限项,仅需调整程序开头的内存空间与程序中的迭代边界即可。
main_s1: 
	call get_fib ;计算下一项 fib 并存入 a3
	call move_fib	 ;将 a2 移动到 a1,a3 移动到 a2
	call transforming ;将 a3 转化为字符串并存入 string
	call show_string ;输出字符串到显存
	loop main_s1

	mov ax, 4c00h
	int 21h






init: ;初始化栈
	mov ax, stack
	mov ss, ax
	mov sp, 40H
	ret






get_fib: ;计算下一项 fib 并存入 a3
	push dx
	push si
	push ax
	push bx
	push cx
	push ds

	mov ax, data
	mov ds, ax

	mov cx, 30 
	mov si, 0
	mov ax, 0
get_fib_s:
	add al, ds:[si] ;利用上一次计算的商(进位)与该位相加
	mov bl, ds:[si + 30]
	add al, bl

	mov bl, 10 ;计算这一位整除 10
	div bl

	mov ds:[si + 60], ah ;将余数存入该位
	mov ah, 0 ;清空余数
	inc si ;将商用于下一次计算
	loop get_fib_s

	pop ds
	pop cx
	pop bx
	pop ax
	pop si
	pop dx
	ret





move_fib: ;将 a2 移动到 a1,a3 移动到 a2
	push dx
	push si
	push ax
	push bx
	push cx
	push ds

	mov ax, data
	mov ds, ax

	mov cx, 30 
	mov si, 0
move_fib_s1: ;将 a2 移动到 a1
	mov al, ds:[si + 30]
	mov ds:[si], al
	inc si
	loop move_fib_s1

	mov cx, 30 
	mov si, 0
move_fib_s2: ;将 a3 移动到 a2
	mov al, ds:[si + 60]
	mov ds:[si + 30], al
	inc si
	loop move_fib_s2

	pop ds
	pop cx
	pop bx
	pop ax
	pop si
	pop dx
	ret

transforming: ;将 a3 转化为字符串并存入 string
	push cx
	push dx
	push si
	push ax
	push bx
	push ds
	push es

	mov ax, data
	mov ds, ax

	mov ax, string
	mov es, ax

	mov cx, 30 
	mov si, 0
transforming_s1: ;将 a3 转化为字符串并存入 string
	mov al, ds:[si + 30]
	add al, '0' ;数字加 '0' 变为数字字符
	mov es:[si], al
	inc si
	loop transforming_s1

	mov cx, 15 
	mov si, 0
	mov bx, 29
transforming_s2: ;将 stirng 中的内容反向以便于输出
	mov al, es:[si]
	mov dl, es:[bx] ;交换首尾元素
	mov es:[bx], al
	mov es:[si], dl
	inc si
	sub bx, 1
	loop transforming_s2

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





show_string: ;输出字符串到显存
	push cx
	push dx
	push si
	push ax
	push bx
	push ds
	push es

	
	mov ax, position ;当前输出的位置
	mov ds, ax
	mov bx, ds:[0]
	mov cl, 000fh ;color

  mov ax, string
	mov ds, ax
	mov si, 0

	mov ax, 0b800h 
	mov es, ax

	mov al, cl
	mov ch, 0
	mov si, 0
	mov ah, 0
show_string_s:
	mov cl, ds:[si]
	jcxz show_string_ok

	sub cl, '0'
	add cl, ah
	jcxz show_string_is_0 ;忽略前导零

	sub cl, ah
	mov ah, 1
	add cl, '0'
	mov es:[bx], cl ;输出该位
	inc bx
	mov es:[bx], al
	inc bx
	
show_string_is_0:
	inc si
	jmp short show_string_s

show_string_ok:
	mov cl, ' ' ;输出空格
	mov es:[bx], cl
	inc bx
	mov es:[bx], al
	inc bx

	mov ax, position ;将当前输出位置存入内存
	mov ds, ax
	mov ds:[0], bx


	mov cx, 30
	mov ax, string
	mov ds, ax
	mov si, 0
show_string_s2: ;清空字符串
	mov ds:[si], 0
	inc si
	loop show_string_s2

	pop es
	pop ds
	pop bx
	pop ax
	pop si
	pop dx
	pop cx
	ret
code ends
end

求 1e8 内的质数

出这么几把提老师是恼弹?

地球人都知道的的性质是合数 \(x\) 至少有一个小于 \(\sqrt x\) 的因子,于是试除时仅需枚举到 \(\sqrt x\) 级别即可。

原理是压位高精,把两个 16 位当 32 位用,并且手动处理进位和借位。求余运算则用多次减法替代。

复杂度上界 \(O(n\sqrt n)\) 再乘上减法模拟求余的超大常熟,跑的超级慢。

加了点优化,从 3 开始枚举并每次加 2,枚举因子试除时从小到大枚举,能快一点是一点呃呃。

这逼东西怎么检查啊、、、

assume cs:code, ss:stack
zero segment
	db 0
zero ends

value segment ;存当前要检查是否为质数的值
	dw 3, 0
value ends

stack segment ;栈
	dw 40H dup(0)
stack ends

code segment
main:
	call init ;初始化栈
	
	mov dl, '2' ;先输出 2 和空格
    mov ah, 2
    int 21h 
	mov dl, ' '
    mov ah, 2
    int 21h 

	mov cx, 1000 ;二重循环来枚举 3~1e7 的值
main_s1:
	push cx
	mov cx, 10000
	main_s2:
		call check ;检查当前枚举到的 value 是否为质数
		push cx
		mov cx, ax
		jcxz main_s3
		call show
	main_s3:
		pop cx
		call get_next ;value += 2
		call get_next
		sub cx, 1
		loop main_s2
	pop cx
	loop main_s1

	mov ax, 4c00h
	int 21h






init: ;初始化栈
	mov ax, stack
	mov ss, ax
	mov sp, 40H
	ret






get_next: ;计算下一项
	push dx
	push si
	push ax
	push bx
	push cx
	push es
	push ds

	mov ax, value
	mov es, ax
	mov ax, es:[0] ;ax 为低位
	mov bx, es:[2] ;bx 为高位
	add ax, 1

	cmp ax, 10000
	jb get_next_done

	mov ax, 0 ;低位超过 10000 则进位
	add bx, 1

get_next_done:
	mov es:[0], ax
	mov es:[2], bx

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




check:
	; mov ax, 1
	; ret

	push cx
	push dx
	push si
	push bx
	push es

	mov ax, value
	mov es, ax
	mov ax, es:[0]
	mov bx, es:[2]
	mov dx, 2

	mov cx, 9999 ;确定试除的上界
	cmp bx, 1
	jae check_s1

	cmp ax, 3
	jbe check_is_prime

	mov cx, ax
	sub cx, 1
check_s1:
	mov ax, value
	mov es, ax
	mov ax, es:[0]
	mov bx, es:[2]
	check_s2: ;使用当前 cx 的值对 value 进行试除,减法模拟求余运算
		cmp ax, dx
		ja check_minus
		jb check_s3

		cmp bx, 0
		je check_is_not_prime ;恰好整除则非质数

		check_s3:
			cmp bx, 1 ;向高位借位,借不到则无法整除,检查下一个 cx
			jb check_s4
			sub bx, 1
			add ax, 10000
			check_minus:
				sub ax, dx ;做减法模拟求余运算
				jmp check_s2

	check_s4:
	sub cx, 1
	inc dx
	cmp cx, 1
	ja check_s1

check_is_prime:
	mov ax, 1
	jmp check_done
check_is_not_prime:
	mov ax, 0
check_done:
	pop es
	pop bx
	pop si
	pop dx
	pop cx
	ret





show: ;输出字符串
	push cx
	push dx
	push si
	push ax
	push bx
	push es

	mov ax, zero
	mov ds, ax
	mov ds:[0], 0

	mov ax, value
	mov es, ax
	mov bx, es:[2] ;先输出高位
    
	mov  cx, 1000
    call dout  
    mov  cx, 100
    call dout    
    mov  cx, 10
    call dout
    mov  cx, 1
    call dout

	mov bx, es:[0] ;再输出低位
	mov  cx, 10000
    call dout
	mov  cx, 1000
    call dout
    mov  cx, 100
    call dout
    mov  cx, 10
    call dout
    mov  cx, 1
    call dout

	mov dl, ' '
    mov ah, 2
    int 21h          ;输出一个空格

	pop es
	pop bx
	pop ax
	pop si
	pop dx
	pop cx
	ret


dout:
	push dx
	push si
	push ax
	push es

    mov  dx,0     ;dx清0,除cx时,被除数为dx,ax
    mov  ax,bx    ;将bx值(第一次为输入的数,随后为余数)赋值给ax
    div  cx       ;(dx,ax),实际为ax(dx==0)除以cx(cx值在调用程序前设置,作为参数传递进来)
    xchg ax,dx    ;ax与dx交换内容。交换后:ax中为余数,dx中为商
    mov  bx,ax    ;将ax值(余数)赋予bx(进入下一轮运算) 
                  ;如果用户前面输入65535,那么在第一轮除以10000后,dx中值为6,bx中值为5535
    cmp  dl,0     
    jne  outanum  ;如果dx中值不为0,则直接输出相应的数值
    
	mov ax, zero
	mov ds, ax
	cmp  ds:[0], 0   ;如果dx中值为0,那么判断是前面无意义的0,还是中间有意义的0。
                  ;如305,那么如果不进行次判断将输入00305。通过此位可以不输出前面两个0,但是输出中间0。
    je   con      ;如果是前面无意义的0 ,则不输出
 outanum:
    mov ds:[0], 1 ;如果输出了一个大于0的数字,则置标志位为1,使得其后所有0都会被输出
	
	add  dl, 30h   ;dl中数值加上30h,变成对应的ASCII码。
    mov  ah, 2
    int  21h      ;输出该数字

con:
	pop es
	pop ax
	pop si
	pop dx
	ret           

code ends
end

冒泡排序

十个数。

stack segment ;栈
	dw 40H dup(0)
stack ends

d1  segment
    a  dw  9, 8, 7, 6, 5, 4, 3, 2, 1, 0
    crlf db 0dh,0ah,'$'
d1  ends      
data  segment
  zero db 0
data  ends

code segment     
  main proc far  
    assume cs:code, ds:d1, ss:stack
	  mov sp, 40H

  start:         
    push  ds     
    sub  ax,ax   
    push ax          
    mov  ax,d1 
    mov  ds,ax
    
    mov cx,10
    mov  si,0
lop0:
    push  cx
    mov  bx,a[si]
    call decout       ;输出一个数值
    assume cs:code,ds:d1
    mov  dl,' '
    mov  ah,2
    int  21h          ;输出一个空格
    add  si,2
    pop  cx
    loop  lop0
    lea dx,crlf
    mov ah,9
    int 21h
    
    mov  cx,9
 lop1:
    push cx
    mov  bx,0
 lop2:
    mov  ax,a[bx]
    cmp  ax,a[bx+2]
    jbe  con
    xchg ax,a[bx+2]
    mov  a[bx],ax
con:
    add  bx,2
    loop lop2

    pop  cx
    loop lop1   


    mov cx,10
    mov  si,0
lop4:
    push  cx
    mov  bx,a[si]
    call decout
    assume cs:code,ds:d1
    mov  dl,' '
    mov  ah,2
    int  21h
    add  si,2
    pop  cx
    loop  lop4

    ret          
  main endp  

decout proc far  
    assume cs:code,ds:data
    push  ds


    mov  ax,data
    mov  ds,ax

    cmp  bx,0
    jne  next   
    mov  dl,'0'
    mov  ah,2
    int  21h
    jmp  return

next:
    mov  zero,0
    
    mov  cx,10000 
    call dout     
                 
    mov  cx,1000
    call dout
    
    mov  cx,100
    call dout
    
    mov  cx,10
    call dout
    
    mov  cx,1
    call dout
    
return:
    pop ds
    ret          
  decout endp
  dout  proc near
    mov  dx,0 
    mov  ax,bx    
    div  cx       
    xchg ax,dx    
    mov  bx,ax     
                  
    cmp  dl,0     
    jne  outanum  
    cmp  zero,0   
                  
    je   decout_con
 outanum:
    mov  zero,1   
    add  dl,30h   
    mov  ah,2
    int  21h      
decout_con:
    ret          
dout  endp  

code ends        
end         

写在最后

写的一坨屎。