「杂文」身为 OIer 的我要在第 7 周速通《汇编语言》,我为什么会做这样的梦(雾)
写在前面
编译器为 MASM-v6.11
一坨屎啊上的课是。
如果看得半懂不懂是正常的,因为我写的匆忙写了一坨屎,不会的都可以问我。
斐波那契数列前 50 项
最多支持输出 30 位十进制数。
输出采用了最原始的直接往显存里写数据,所以这个程序可能在官方钦定的 IDE 上运行不了。
.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 ax, 0
mov ds:[si], ax
inc si
loop show_string_s2
pop es
pop ds
pop bx
pop ax
pop si
pop dx
pop cx
ret
code ends
end
求 1e7 内的质数
出这么几把提老师是恼弹?
地球人都知道的的数论性质是合数 \(x\) 至少有一个不大于 \(\sqrt x\) 的大于 1 的因子。考虑反证,另一个地球人都知道的结论是因数都是成对出现的,即若 \(d\) 是 \(x\) 的因子,则 \(\frac{x}{d}\) 也是 \(x\) 的因子。则若某个合数 \(x\) 的因子均大于 \(\sqrt x\),则有因数 \(d\) 满足 \(d>\sqrt{x}, \frac{x}{d} > \sqrt{x}\),得 \(d\times \frac{x}{d} > x\),荒谬的,则原结论成立。
于是如果要使用试除法判断一个数是否是合数,仅需枚举 \(1\sim \sqrt x\) 即可。
回到这个恼弹实验,因为要判断的数很大,于是考虑使用压位高精,把两个 16 位内存当 32 位内存用,手动处理进位和借位。求余运算则用多次减法替代。
复杂度上界 \(O(n\sqrt n)\) 再乘上减法模拟求余的超大常数,跑的超级慢。
加了点优化,从 3 开始枚举并每次加 2,枚举因子试除时从小到大枚举,能快一点是一点呃呃。
如果真的要输出到 1e7 需要跑到下个月或者明年。
这逼东西怎么检查啊、、、
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 ax, 0
mov ds:[0], ax
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, 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
mov ax, 0
cmp ds:[0], ax ;如果dx中值为0,那么判断是前面无意义的0,还是中间有意义的0。
;如305,那么如果不进行次判断将输入00305。通过此位可以不输出前面两个0,但是输出中间0。
je con ;如果是前面无意义的0 ,则不输出
outanum:
mov ax, 1
mov ds:[0], ax ;如果输出了一个大于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
任意数量有符号整数冒泡排序
任意数量(\(\le 500\))的有符号数的排序。
按行输入要排序的数,当输入了空行时停止输入。
.model large
assume ds:data, cs:code
data segment
buf db ?
db ?
db 10 dup(?)
array dw 500 dup(?)
number dw 1 dup(?)
symbol dw 1 dup(?)
data ends
code segment
main:
mov [number], 0
input:
lea dx, buf ; 输入字符串,到buf中
mov ah, 0ah
int 21h
mov dl, 13 ; 打印换行
mov ah, 02h
int 21h
mov dl, 0ah
mov ah, 02h
int 21h
mov bl, [buf + 2]
cmp bl, 13
jz input_end
call read ; 调用ReadUInt,该函数将buf转换成整数,存放到ax中
mov si, [number]
shl si, 1 ; si = si * 2,一个数字占2个字节,因此乘以2
mov [array + si], ax ; 将读取的数字存放到数组中
shr si, 1
inc si
mov [number], si
jmp input
input_end:
push sp
mov si, 0 ; 冒泡排序,i=si,j=di
mov di, 0
L1:
cmp si, [number]
jge L1_END
mov di, si ; j = i + 1
inc di
L2:
cmp di, [number]
jge L2_END
shl si, 1
shl di, 1
mov ax, [array+si]
mov bx, [array+di]
shr si, 1
shr di, 1
cmp ax, bx
jg exchange
inc di
jmp L2
exchange:
shl si, 1
shl di, 1
mov [array+di], ax
mov [array+si], bx
shr si, 1
shr di, 1
inc di
jmp L2
L2_END:
inc si
jmp L1
L1_END:
mov bx, 0
L3:
cmp bx, [number]
jge L3_END
mov si, bx
shl si, 1
mov ax, [array+si]
call print_int
push bx
push si
mov dl, 13 ; 打印换行
mov ah, 02h
int 21h
mov dl, 0ah
mov ah, 02h
int 21h
pop si
pop bx
inc bx
jmp L3
L3_END:
pop sp
mov ah, 4ch
int 21h
read proc
pushf
push bx
push cx
push dx
push si
push di
mov [symbol], 0
mov cx, 0
mov cl, [buf + 1] ; 获取字符串长度
lea si, [buf + 2] ; 获取字符串地址
mov ax, 0
mov di, 0
add si, di
mov bl, [si]
cmp bl, '-'
jne read_s1
mov [symbol], 1
sub cl, 1
inc di
read_s1:
lea si, [buf + 2]
add si, di ; 获取当前处理的字符地址
inc di
mov bl, [si] ; 获取字符
sub bl, 48 ; 字符减去'0'
mov dl, 10
mul dl ; 乘以10
mov bh, 0
add ax, bx ; 加上数字
loop read_s1
mov bx, [symbol]
cmp bx, 1
jne read_s1_end
neg ax
read_s1_end:
pop di
pop si
pop dx
pop cx
pop bx
popf
ret
read endp
print_int proc
pushf ; save eflags and register
push bx
push cx
push dx
mov [symbol], 0
cmp ax, 0
jz print_zero
js print_minus
jmp skip_print_zero_and_minus
print_zero:
mov dl, '0'
mov ah, 02h
int 21h
jmp print_int_end2
print_minus:
neg ax
mov [symbol], 1
skip_print_zero_and_minus:
mov cx, 0
mov bx, 10
print_int_loop1:
mov dx, 0
div bx
add dl, 30h
push dx
inc cx
cmp ax, 0
jne print_int_loop1
cmp [symbol], 1
jnz print_int_loop2
mov dl, '-'
mov ah, 02h
int 21h
print_int_loop2:
pop dx
mov ah, 02h
int 21h
loop print_int_loop2
print_int_end2:
mov dl, 13 ; 打印换行
mov ah, 02h
int 21h
pop dx
pop cx
pop bx
popf
ret
print_int endp
code ends
end main
写在最后
写的一坨屎。
作者@Luckyblock,转载请声明出处。