如何用匯編語言編寫一個求最大公約數(GCD)的過程——輾轉相除法
選題:《組合語言 基於X86處理器》【Kip Irvine著】—— Chapter7 程式設計練習第6題
兩個數的最大公約數(GCD)是指能整除這兩個數的最大整數。下述虛擬碼描述的是迴圈整數除法的GCD演算法:
int GCD(int x,int y)
{
x = abs(x)
y = abs(y)
do{
int n = x % y
x = y
y = n
}while(y>0)
return x
}
用匯編語言實現該程式,並編寫測試程式,通過向該函式傳遞不同的數值對其進行多次呼叫。
---------------------------------------------------------------------------------------------------------------------------
我們可以從題目得到兩個很重要的資訊:
一、最大公約數的概念:能整除這兩個數的最大整數。
二、演算法思想:
1、兩個數假定x,y;
2、兩者相除取餘;
3、若,餘數不為0,則將除數作為下一次的被除數,商作為下一次的除數;
4、直到找到餘數為零時的商,返回此時商的值。
而我們將上述的演算法思想取了一個好聽的名字叫做“輾轉相除法”,此外,大家可以自行驗證一下該方法是否能準確的求出兩個數值的最大公約數。
接下來我們就來講講,如何使用匯編語言來編寫這樣的一個演算法:
1、首先我們建立asm專案,配置好彙編環境與外部庫路徑;然後我們初始化DWORD型別的A,B兩個數,另外準備一個變數result用於儲存最後找到的最大公約數;此外,另編寫三句提示語便於清晰的顯示:
Include Irvine32.inc .data prompt1 BYTE '第一個數字是:',0 prompt2 BYTE '第二個數字是:',0 prompt3 BYTE '兩個數字的最大公約數是:',0 A DWORD 14750 B DWORD 37550 result DWORD ?
2、然後由於我們想多次輸出過程,所以我們繼續編寫一個輔助顯示的——Display函式;
;---------------------------
Display PROC
;用於被呼叫,顯示結果
;---------------------------
call WriteInt ;呼叫writeInt,輸出數字
call crlf
ret
Display ENDP
3、接著開始構思程式主體,思路大概是:先輸出第一個數字,再輸出第二個數字,然後呼叫演算法求出結果,最後輸出結果;
.code
main PROC
;顯示第一個數字:
mov EDX,OFFSET prompt1 ;用offset獲取顯示語的偏移地址,並存入EDX中
call WriteString ;呼叫WriteString,將顯示語以字串形式顯示出來
mov EAX,A
call Display ;呼叫Display函式
;顯示第二個數字:
mov EDX,OFFSET prompt2
call WriteString
mov EBX,B
call Display
;呼叫演算法:
mov EDX,OFFSET prompt3
call WriteString
call ZhanzhuanMethod ;呼叫輾轉相除函式,求兩個數字的最大公約數
;顯示結果:
mov EAX, result ;將結果result存放入EAX中
call Display ;再次呼叫Display函式,輸出結果
4、然後就是最關鍵的GCD演算法實現了,在這裡我們對先梳理一下演算法思路:
- 將A、B的值分別送入暫存器EAX與EBX中;
- 為了保證被除數大於除數,故使用cmp指令比較A、B大小;
- 被除數大於除數,即使用無符號數跳轉指令JNB(leftOp>=rightOp),其結果為否,則交換;結果為真,則跳轉到L1;
- 編寫L1自身迴圈用於找到餘數為0時的商,其中若找到餘數為0的商則跳,將結果放入result並ret。
;---------------------------
ZhanzhuanMethod PROC ;輾轉相除函式
;用於求最大公約數的方法
;---------------------------
mov EAX, A
mov EBX, B
cmp EAX, EBX ;比較EAX與EBX
JNB L1 ;EAX大於或等於EBX,則跳轉到L_1
mov B, EAX ;若上一步為否,則:交換位置,避免除數大於被除數
mov A, EBX
L1:
mov EAX, A ;已交換/預設不變(滿足條件時),保證了A > B
mov EBX, B
mov EDX, 0 ;初始化EDX為0
div EBX ;A%B,兩個數字相除,餘數自動存入EDX中
cmp EDX, 0 ;比較EDX與0
JE L_End ;JE指令為等於跳轉。若餘數為0,則找到了最終的最大公約數,跳轉到L_End
mov EAX, B ;若上一步餘數不為0,則將 除數B 放入 EAX 中
mov A, EAX ;再把原除數B,通過EAX傳遞給A,作為用作下一次操作的 被除數
mov B, EDX ;再把EDX內的餘數,賦給B,作為下一次操作的 除數 (從而再次保證了A > B)
jmp L1
L_End:
mov result,EBX
ret
ZhanzhuanMethod ENDP
5、最後別忘了關鍵的“尾巴”:main ENDP
END main
那麼到這裡,本題就算是成功的解決了,經過最終的除錯,我們可以看到除錯結果如下:
在環境搭建與連結庫路徑匯入無誤的情況下,程式到底能不能run呢?感興趣的小夥伴們趕快試一下吧!
----------------------------------------------------------------------------------------------------------------------------
附:(完整程式碼)
Include Irvine32.inc
.data
prompt1 BYTE '第一個數字是:',0
prompt2 BYTE '第二個數字是:',0
prompt3 BYTE '兩個數字的最大公約數是:',0
A DWORD 14750
B DWORD 37550
result DWORD ?
.code
main PROC
;顯示第一個數字:
mov EDX,OFFSET prompt1 ;用offset獲取顯示語的偏移地址,並存入EDX中
call WriteString ;呼叫WriteString,將顯示語以字串形式顯示出來
mov EAX,A
call Display ;呼叫Display函式
;顯示第二個數字:
mov EDX,OFFSET prompt2
call WriteString
mov EBX,B
call Display
;呼叫演算法:
mov EDX,OFFSET prompt3
call WriteString
call ZhanzhuanMethod ;呼叫輾轉相除函式,求兩個數字的最大公約數
;顯示結果:
mov EAX, result ;將結果result存放入EAX中
call Display ;再次呼叫Display函式,輸出結果
;---------------------------
ZhanzhuanMethod PROC ;輾轉相除函式
;用於求最大公約數的方法
;---------------------------
mov EAX, A
mov EBX, B
cmp EAX, EBX ;比較EAX與EBX
JNB L1 ;EAX大於或等於EBX,則跳轉到L_1
mov B, EAX ;若上一步為否,則:交換位置,避免除數大於被除數
mov A, EBX
L1:
mov EAX, A ;已交換/預設不變(滿足條件時),保證了A > B
mov EBX, B
mov EDX, 0 ;初始化EDX為0
div EBX ;A%B,兩個數字相除,餘數自動存入EDX中
cmp EDX, 0 ;比較EDX與0
JE L_End ;JE指令為等於跳轉。若餘數為0,則找到了最終的最大公約數,跳轉到L_End
mov EAX, B ;若上一步餘數不為0,則將 除數B 放入 EAX 中
mov A, EAX ;再把原除數B,通過EAX傳遞給A,作為用作下一次操作的 被除數
mov B, EDX ;再把EDX內的餘數,賦給B,作為下一次操作的 除數 (從而再次保證了A > B)
jmp L1
L_End:
mov result,EBX
ret
ZhanzhuanMethod ENDP
;---------------------------
Display PROC
;用於被呼叫,顯示結果
;---------------------------
call WriteInt ;呼叫writeInt,輸出數字
call crlf
ret
Display ENDP
main ENDP
END main