編寫無溢出除法的彙編子程序(轉載)

原文連接:https://blog.csdn.net/ljianhui/article/details/17457317

 
 
1、爲何除法會溢出
看到這個標題,你可能會問彙編中不是有div指令來實現除法運算嗎?爲何咱們還要本身寫一個子程序來實現除法?爲了說明咱們爲何須要本身寫一個實現除法的子程序,還得從除法爲何會發生溢出提及。
 
在彙編中,若是要使用除法運算,咱們可使用div指令,它實現的就是除法的功能,可是它是一個很是容易,甚至說不可避免會發生溢出的指令,下面來看看它的工做方式,咱們就能知道箇中源由。注:這裏所說的除法溢出並非指分母爲0而發生溢出的狀況。
 
div的工做方式:
(1)除數:有8位和16位兩種,在一個寄存器或內存單元中
(2)被除數:默認放在AX或DX和AX中,若是除數爲8位,則被除數爲16位,默認在AX中存放;若是除數爲16位,被除數爲32位,在DX和AX中存放,DX存放高16位,AX存放低16位
(3)結果:若是除數爲8位,則AL(AX的低8位)存儲除法操做的商,AH(AX的高8位)存儲除法操做的餘數;若是除數爲16們,則AX存儲除法操做的商,DX存放除法操做的餘數。
 
用一個表格來表示上述的工做方式,以下表所示:
除數
被除數
結果
8位
16位,AX
商:AL,餘數:AH
16位
32位,DX(高16位)+AX(低16位)
商:AX,餘數:DX
 
就這麼一看彷佛尚未什麼問題,下面我就以一個例子來講明一下種工做方式下的除法的致命缺陷。
 
爲了更加容易地說明,咱們以除數爲8位的狀況來講明,假設咱們的被除數爲65535(16位的最大值)存儲在AX中,除數爲1,存儲在CL中,而後執行除法指令: div CL。根據上面的說法,結果都是放在AX中的,餘數爲0,固然這沒有問題,然而商爲65535要放在AL中倒是放不下的,由於AL能存放的最大值只爲255,因此此時就會發生溢出。咱們能夠看到65535/1 = 255,這顯然與我位正常的除法結果不符。
 
在這裏你能夠認爲我舉了一個很是極端的例子,可是這種狀況卻並非極端的狀況,對於除數爲8位的除法,只要商在255以上就會發生這種溢出。除數爲16位的原理及狀況與這裏相同,只是它是當商爲65535以上是就會發生溢出而已。
 
2、如何解決這個溢出問題
既然咱們知道了問題的根源,要解決它就不困難了。爲了統一併且不發生溢出,咱們能夠把全部的除法的商都用32位來保存,即全部的被除數都用32位,全部的除數都用16位,全部的商都用32位,全部的餘數都用16位來保存,就能夠解決這個問題,由於一個數除以一個整數後,不可能大於其以前的值。而對於8位的除法,除數和被除數的高位全用0補全便可。爲了達到這個目的,咱們就不能使用默認的除法指令div了,而須要咱們寫代碼來實現咱們自定義的除法。
 
考慮到除法是一個經常使用的操做,因此咱們能夠編寫一個子程序來實現除法,在進行除法運算時,直接調用咱們本身定義的子程序來完成任務,而不直接使用div指令。
 
3、如何實現這個功能
要實現除法還得使用div指令,只是咱們能夠作一些特殊的處理,讓咱們自定義的除法不發生溢出,由於咱們使用的是除數爲16位的除法,也就是說,咱們只要能保證除法的商不大於65535就不會發生問題。爲了實現這個功能,咱們首先要知道一個關於除法的公式(H表示X的高位,L表示X的低位):
X/N = int(H/N)* 2^16 + [rem(H/N)* 2^16+L]/N
 
這個公式告訴咱們32位的被除數與16位的除數,能夠拆分爲兩個除數和被除數都爲16位的數的除法,而後經過加法來獲得一樣的結果,這個是很是重要的。由於咱們能夠把H和L獨立起來考慮,當進行H/N時,由於H存儲在DX中,咱們能夠先把DX中的內容複製到AX中(操做以前把AX的內容保存好),再把DX的內容置爲0,這樣產生的除法操做的商就必定能放在一個16位的寄存器AX中。對於公式中的其餘除法運算也採用相同的操做,而後經過把除法以前產生的結果進行相加,就能產生咱們的無溢出除法子程序。
 
4、實現代碼
基於這個公式,咱們實現的代碼以下:
 
   
;子程序名稱:divdw
;功能:進行不會產生溢出的除法運算,被除數爲dword型
;	   除數爲word型,結果爲dword型
;參數:	(ax)=dword型數據的低16位
;		(dx)=dword型數據的高16位
;		(cx)=除數
;返回:	(dx)=結果的高16位,(ax)=結果的低16位
;		(cx)=餘數
;計算公式:X/N=int(H/N)*2^16+[rem(H/N)*2^16+L]/N
divdw:
	jcxz divdw_return	;除數cx爲0,直接返回
	push bx			;做爲一個臨時存儲器使用,先保存bx的值
		
	push ax			;保存低位
	mov ax, dx		;把高位放在低位中
	mov dx, 0		;把高位置0
	div cx			;執行H/N,高位相除的餘數保存在dx中
	mov bx, ax		;把商保存在bx寄存器中
	pop ax			;執行rem(H/N)*2^16+L
	div cx			;執行[rem(H/N)*2^16+L]/N,商保存在ax中
	mov cx, dx		;用cx寄存器保存餘數
	mov dx, bx		;把bx的值複製到dx,即執行int(H/N)*2^16
						;因爲[rem(H/N)*2^16+L]/N已保存於ax中,
						;即同時完成+運算
	pop bx			;恢復bx的值
	divdw_return:
	ret
 
   

  

 
   
5、程序分析
一、
爲了消除分母爲0的狀況,咱們在子程序的一開始處就判斷分母CX的值,若其值爲0,則直接返回。
 
二、在分析時必定要記住,div把DX看成高16位來看,把AX看成低16來看,咱們的子程序的調用者也是如此。因此當程序解釋DX的值時,其值爲真實值乘以2^16,例如DX中的值爲1,AX中的值也爲1,則由於DX爲高16位,因此被解釋爲1*2^16 = 65536,則AX做爲低16位來處理,其值爲1。因此32位的數的值爲65536+1 = 65537,也就是說32位數的值爲DX*2^16+AX。
 
  push ax ;保存低位
  mov ax, dx ;把高位放在低位中
  mov dx, 0 ;把高位置0
  div cx ;執行H/N,高位相除的餘數保存在dx中
就是前面所說的把把H和L獨立起來考慮,當H/N時,先把DX中的內容(即H)複製到AX中,再把DX的內容置爲0,而後再與CX相除。
 
三、
  pop ax ;執行rem(H/N)*2^16+L
  div cx ;執行[rem(H/N)*2^16+L]/N,商保存在ax中
由於前面的除法操做即H/N的餘數保存在DX中,又因爲在除法操做div時,DX是當高位來處理的,因此在DX中的餘數的值就會至關於其原來的值乘以2^16,而後再把以前保存在棧中的X的低位L恢復到ax中來,div指令把AX看成低16位來處理,因此也就至關於DX*2^16+AX,也就是rem(H/N)*2^16+L,以後的除法就不用解釋了。
 
四、
  mov cx, dx ;用cx寄存器保存餘數
把前面div操做中產生的餘數(保存在dx中)複製到cx中來,由於根據函數的說明,咱們是用cx來保存餘數的,而不是默認的dx。
 
五、
  mov dx, bx
因爲bx先前保存着H/N的商,而DX又是高16位,因此把bx的值恢復到dx中就至關於int(H/N)*2^16,因爲AX是低16位,而以前的操做又已經把結果(即[rem(H/N)*2^16+L]/N的商)保存在AX中,因此這步結束後,也就至關於執行完int(H/N)*2^16+[rem(H/N)*2^16+L]/N的整個操做完成了。
 
能夠看到,這段看來小小的代碼就這樣有很是巧妙的方式,利用了div指令的特性,把DX看做高16位和把AX看做低16位和餘數保存在DX中,商保存在AX中的特性,從而實現了咱們的無溢出除法的功能了。
相關文章
相關標籤/搜索