Ch 13 BCD 數(1):BCD 簡介


BCD 概念

前面的幾章堙A談到十六進位數及二進位數,在這一章堙A小木偶想談談 BCD 數。何謂 BCD 數呢?我們已經知道 4 個位元最多可以表示 16 個數字,若以十六進位表示可以由 00H 到 0FH,以十進位表示可以由 00D 到 15D,而 BCD 數則是只用到前面的 10 個數來表示十進位,也就是說 BCD 數中不可能有 0A、0B、0C 等英文字出現。

BCD 數的英文是 binary-coded decimal,就字面上意義即為『二進位編碼的十進位數』,意思就是以 4 個位元的方式來表示十進位,因此有許多空間是浪費掉了,但是卻可以利用我們對十進位的熟悉,很方便的編寫程式。一般而言,BCD 數可以分成兩種,聚集的 BCD 數 (packed BCD) 和非聚集的 BCD 數 (unpacked BCD)。

雖經上述說明 BCD 數的意義,但相信還是有許多人不懂,讓小木偶舉個例子吧。例如你在 DEBUG 中輸入 AX 暫存器的數值為 0908,以非聚集 BCD 數來看表示是十進位的 98,若是以聚集 BCD 數來看是表示十進位的 908﹔如果是以十六進位來看是表示十進位的 2312。這樣說你可能會被搞糊塗了,前面不是說 AX 內的數值為十六進位嗎?怎麼一下變 98 ,一下變 908,一下又變成 2312 呢?其實這完全是看程式設計師所寫的程式如何去解釋 AX 內的數值。

當程式以類似第七或第八章的方式將 AX 暫存器的數值印出來時,AX 的數值明顯是十六進位數字。但是如果程式以下面的片段列印出來,你猜猜看在螢幕上會顯示什麼數字?在 DEBUG 內,以 A 指令輸入下面程式片段:(黃色是您要輸入的字,綠色是 BEBUG 顯示的字,[Enter]是表示按下 Enter 鍵)。

C:\WINDOWS\COMMAND>debug [Enter]
-a [Enter]
1C6C:0100 mov ax,908 [Enter]
1C6C:0103 mov dl,ah [Enter]
1C6C:0105 add dl,30 [Enter]
1C6C:0107 push ax [Enter]
1C6C:0108 mov ah,2 [Enter]
1C6C:010A int 21 [Enter]
1C6C:010C pop ax [Enter]
1C6C:010D mov dl,al [Enter]
1C6C:010F add dl,30 [Enter]
1C6C:0112 mov ah,2 [Enter]
1C6C:0114 int 21 [Enter]
1C6C:0116 [Enter]
-g 116 [Enter]
98

雖然 AX 之值為 908,但是我們也可以把它看成是 98,而且顯示成 98,此時 AX 之數值為非聚集的 BCD 數。當然你也可以把它顯示成 908,此時 AX 數值為聚集的 BCD 數。或是像第七、第八章的方式顯示成 2312,這時 AX 之數值是十六進位數。所以小木偶說,在電腦中的數值是隨程式解釋而不同。

若我們用 ADD、SUB、MUL、DIV 等 80X86 指令集做運算,CPU 會自動的把 所用到的運算元 (數值) 當作十六進位。如果我們要把這些數值當作 BCD 數,有兩種方法可以達成,一種是另外建立新的指令專作 BCD 數的運算;另外一種方法是先把他們當作十六進位,運算後如果超過九,再做調整。而 80X86 CPU 是採用後者。

80X86 在做 BCD 數值運算時,必定用 AL 作為調整的對象,因此必定用 AL 當作其中的一個運算元。舉例來說例如想作 8+7 的非聚集 BCD 運算,最後在 AL 暫存器應該會得到 0105 這個數,小木偶用 DEBUG 來說明:

C:\WINDOWS\COMMAND>debug [Enter]
-a [Enter]
1C6C:0100 mov al,8 [Enter]
1C6C:0102 add al,7 [Enter]
1C6C:0104 aaa [Enter]
1C6C:0105 [Enter]
-t [Enter]

AX=0008  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=1C6C  ES=1C6C  SS=1C6C  CS=1C6C  IP=0102   NV UP EI PL NZ NA PO NC
1C6C:0102 0407          ADD     AL,07
-t [Enter]

AX=000F  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=1C6C  ES=1C6C  SS=1C6C  CS=1C6C  IP=0104   NV UP EI PL NZ NA PE NC
1C6C:0104 37            AAA
-t [Enter]

AX=0105  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=1C6C  ES=1C6C  SS=1C6C  CS=1C6C  IP=0105   NV UP EI PL NZ AC PO CY
1C6C:0105 0475          ADD     AL,75
-

在位址 1C6C:0100 將 8 存入 AL 暫存器,下一個指令是把 7 加到 AL 暫存器內,完成後,看到 AX 暫存器是 000F(紅色字),這是理所當然的,因為 ADD 指令本來就是將這些數字當作十六進位來看。再下一行是 AAA 指令,這一個指令就是把 AL 內的十六進位數做適當的調整,使其變為非聚集的 BCD 數。AAA 這個指令會檢查 AL 是否超過 9,如果是的話就會產生進位,使 AH 為一,並使 AL 暫存器減 0AH,所以當 AAA 指令結束後,AX 之內容並非 000F 反而是 0105。

AAA 這個 80X86 的指令,就是在做完加法後將 AL 之內容調整成非聚集的 BCD 數,因其加上 3030h 很快就變成 ASCII 數字就可印在螢幕上,AAA 這三個字母是 ASCII adjust for addition 之意。假如要將其調整成聚集的 BCD 數,過程相同,只是將 AAA 指令換成 DAA 指令,其意義為decimal adjust for addition,請看下面 DEBUG 之例子。

C:\WINDOWS\COMMAND>debug [Enter]
-a [Enter]
1C6C:0100 mov al,8 [Enter]
1C6C:0102 add al,7 [Enter]
1C6C:0104 daa [Enter]
1C6C:0105 [Enter]
-t [Enter]

AX=0008  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=1C6C  ES=1C6C  SS=1C6C  CS=1C6C  IP=0102   NV UP EI PL NZ NA PO NC
1C6C:0102 0407          ADD     AL,07
-t [Enter]

AX=000F  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=1C6C  ES=1C6C  SS=1C6C  CS=1C6C  IP=0104   NV UP EI PL NZ NA PE NC
1C6C:0104 27            DAA
-t [Enter]

AX=0015  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=1C6C  ES=1C6C  SS=1C6C  CS=1C6C  IP=0105   NV UP EI PL NZ AC PO NC
1C6C:0105 0475          ADD     AL,75

其他的四則運算,也都類似,減法調整成非聚集 BCD 數的指令為 AAS(ASCII adjust for subtraction ) ,調整成聚集 BCD 數的指令為 DAS(decimal adjust for subtraction)。乘除法只有調整成非聚集 BCD 數的指令,分別是 AAM 和 AAD (ASCII adjust for multuply 和 ASCII adjust for division )。小木偶將之整理成下表:

加法減法乘法除法
非聚集調整AAAAASAAMAAD
聚集調整DAADAS

非聚集 BCD 數的加法

一般的計算機只能顯示約八位數的大小(一位數是指只有個位數,二位數是指個位數和十位數,三位數是指個、十和百位數,故八位數能計算至千萬位數),但現在小木偶要寫一個程式,能擴充成可以計算很大位數的加法程式,但是此處小木偶只寫五位數相加的程式。

雖然 80X86 指令只提供 AL 暫存器作為 BCD 數加法運算之用,長度僅僅 8 位元而已,但是我們可以一次只運算一位就能算出很大位數的加法,就像小學時代,國小老師教我們的直式加法運算類似。

  被加數:          9 3 5 0 4
    加數:    +     6 1 6 8 9
  ----------------------------
      和:                9 3

就像上式一樣,當我們運算時,先由個位數開始相加,如果有進位則下一次做十進位數相加時,要記得加一,只要重複上述步驟,就能將運算很大數的加法。小木偶把原始程式命名為『BCD_ADD.ASM』,其內容如下:

digit   equ     5
;***************************************
code    segment                 ;03
        assume  cs:code,ds:code ;04
        org     100h            ;05
;---------------------------------------
main    proc    far             ;07
start:  mov     si,offset v1    ;08
        mov     cx,digit        ;09
        call    ubcd_print      ;10 印出被加數
        mov     dl,'+'          ;11 印出『+』字
        mov     ah,2
        int     21h
        mov     si,offset v2    ;14 印出加數
        mov     cx,digit
        call    ubcd_print      ;16
        mov     dl,'='          ;17 印出『=』字
        mov     ah,2            ;18
        int     21h             ;19

;開始計算
        mov     ax,offset v1    ;22
        mov     bx,offset v2
        mov     dx,offset sum
        mov     cl,digit
        mov     ch,digit
        call    ubcd_add        ;27

        mov     si,offset sum   ;29 印出和
        mov     cx,digit+1
        call    ubcd_print      ;31

        mov     ax,4c00h        ;33 結束
        int     21h             ;34
main    endp
;---------------------------------------
;在螢幕上印出非聚集 BCD 數      ;37
;輸入:SI:指向非聚集BCD數字最低位址,最大位數於高位址
;      CX:欲印出之非BCD數的位數,若CX為零則此副程式會自動尋找(要注意資料正確)
;輸出:所有暫存器均不變
ubcd_print      proc    near
        push    ax              ;42 保存暫存器之值
        push    cx
        push    dx
        jcxz    u_pt0           ;45 判斷是否需要自動尋找BCD最高位址

        push    si              ;47 不須自動尋找BCD最高位址
        add     si,cx           ;48 因為該最高位址即等於SI+CX,並存於SI
        pop     cx
        jmp     short u_pt2     ;50

;自動尋找 BCD 之最高位址(向高位址處找到第一個不是阿拉伯數字的位址)並存於SI
u_pt0:  mov     cx,si           ;53 欲印出之BCD數之位址存於CX
u_pt1:  cmp     byte ptr [si],0
        jb      u_pt2
        cmp     byte ptr [si],9
        ja      u_pt2
        inc     si
        jmp     u_pt1           ;59

;開始印出數字,但最大位數為零時,0不印出。SI指向要印出的BCD數的某一位
u_pt2:  dec     cx              ;62 CX之值為BCD數的最低位址減一
u_pt3:  dec     si              ;63 若最前面位數為零則不印出
        cmp     si,cx           ;64 檢查是否已經是該 BCD 數的最低位址了
        je      u_pt5           ;65 若是,則表示該 BCD 數為零
        cmp     byte ptr [si],0
        je      u_pt3

u_pt4:  mov     dl,[si]         ;69 已經找到最大位數不是零,並開始印出
        add     dl,'0'
        mov     ah,2
        int     21h
        dec     si
        cmp     si,cx
        jne     u_pt4           ;75

        pop     dx              ;77 將原數值存回暫存器
        pop     cx
        pop     ax
        inc     si
        ret                     ;81 返回父程式

u_pt5:  inc     si              ;83 想要印出的BCD數為零
        jmp     u_pt4
ubcd_print      endp            ;85
;---------------------------------------
;計算兩非聚集 BCD 數之加法
;輸入:AX:被加數位址
;      BX:加數位址
;      DX:和位址
;      CH:被加數位數
;      CL:加數位數
;輸出:於DI所指的位址列出和
;      AX、BX、CX、DX均被破壞
ubcd_add        proc    near
        push    si      ;96 保存SI、DI
        push    di
        mov     si,ax   ;98 SI=被加數最低位址
        mov     di,dx   ;99 DI=加數最低位址
        cmp     ch,cl   ;100 比較被加數與加數那一個位數大
        jae     u_a0
        xchg    ch,cl   ;102 若加數位數大,則使被加數與加數交換最低
        xchg    bx,si   ;103 位址(SI與DI),同時也交換位數(CH、CL)

;開始相加時,SI指向兩較大位數的位址,CH為較大位數
u_a0:   sub     ch,cl
        sub     ax,ax
        clc

;計算重疊部份
u_a1:   mov     al,[bx]	;111 取得加數中的一位
        adc     al,[si]	;112 加上被加數相對應的一位
        aaa		;113 加法調整
        inc     si	
        mov     [di],al	;115 存入和內
        inc     bx
        inc     di
        dec     cl
        jnz     u_a1
        jcxz    u_a3	;120 假如被加數與加數位數相等時,CX會等於零

;計算只有較大位數部份
u_a2:   mov     al,[si]	;123
        adc     al,0	;124 使AL加上零,再加上進位旗標
        aaa
        inc     si
        mov     [di],al
        inc     di
        dec     ch
        jnz     u_a2	;130

;處理最大位數進位
u_a3:   mov     al,cl	;133 使AL等於零
        adc     al,0	;134 使AL加上零,再加上進位旗標
        mov     [di],al	;135 存入和中

        pop     di
        pop     si
        ret
ubcd_add        endp
;---------------------------------------
v1      db      4,0,5,3,9       ;142 被加數=93504,十進位的九萬三千五百零四
v2      db      9,8,6,1,6       ;143 加數=61689
sum     db      digit+1 dup (?) ;144 和
;---------------------------------------
code    ends
;***************************************
        end     start

先看第 142 行到第 144 行的資料部份,這一部份是存放被加數(v1) 及加數(v2) 的 BCD 數,在這裡要注意一件事,當我們書寫數字時,左邊的數字是較大位數(例如 93504 中 9表示萬位數比千位數大)。但是在電腦中表示數值的方式,一般是大位數位於高位址,也就是說 9 所在的位址要比 3 的位址高一個位元組。假如你用 DEBUG 來觀察,應該會變成像下面這樣。

H:\HomePage\SOURCE>debug bcd_add.com [Enter]
-r [Enter]
AX=0000  BX=0000  CX=00C0  DX=0000  SP=FFFE  BP=0000  SI=0000  DI=0000
DS=1C8E  ES=1C8E  SS=1C8E  CS=1C8E  IP=0100   NV UP EI PL NZ NA PO NC
1C8E:0100 BEB001        MOV     SI,01B0
-d 1a0 L20 [Enter]
1C8E:01A0  88 05 47 FE CD 75 F3 8A-C1 14 00 88 05 5F 5E C3   ..G..u......._^.
1C8E:01B0  04 00 05 03 09 09 08 06-01 06 00 00 00 00 00 00   ................

看到紅色的部份,萬位數是在較高位址(1C8E:01B4),是在右邊;個位數在低位址(1C8E:01B0),是在左邊,看起來跟一般的書寫方式不同,很不習慣。或許您也可以依我們平時書寫的習慣,將大位數放在左邊,也就是低位址處,在撰寫程式時,是沒什麼問題的,但是不知有沒有硬性規定不能這樣寫。

或許您會問,小木偶怎知道 1C8E:01B0 就是被加數的位址呢?原來當 DEBUG 載入程式時,CX 的大小是載入檔案之長度,而 COM 檔是從 0100H 處開始,故 CX 加上 100H 就是檔案最後,而小木偶所寫的程式加數與被加數均在檔案最後。所以一進入 DEBUG 後,小木偶立即用『r』指令看看 CX 之大小。(但是如果是由 DOS 載入程式的話,暫存器的初始值是無意義的,要特別小心,小木偶在撰寫下一章的程式時,因粗心而誤認由 DOS 載入跟 DEBUG 一樣,結果多花了兩天的工夫才找到錯誤。)

第 144 行,和的位數必須比被加數或加數的位數還要多一,這理由應該並不難想像。因為兩數相加,有可能會有進位產生,考慮兩個最大的五位數,即 99999 相加,得到的和是六位數,所以和的位數必須比被加數或加數多一。

ubcd_print 副程式

接下來,來看看程式第 37 到第 85 行的 ubcd_print 副程式,這個副程式是把非聚集的 BCD 數印在螢幕上。

不管電腦上大位數放在左邊或右邊,要把 BCD 數印出來時,最大位數 (對 BCD_ADD.COM 而言,最大位數就是萬位數) 應最先印出來,並且在最左邊。所以這個副程式把該保留的暫存器存入堆疊後,第二要做的就是找到最大位數的位址。程式第 45 行到第 59 行就是尋找最大位數。

如果 CX 不等於零,表示副程式提供了要印出 BCD 數的位數,而 SI 又提供了該數的最低位址,故該數的最大位數位址不難求出 (見第 47 行到第 50 行)。如果 CX 為零,就得由 ubcd_print 副程式負責求出,原理如下,因為非聚集 BCD 數必是由 0、1、2、3……9 等十個阿拉伯數字組成,所以小木偶設計由 SI 向高位址處搜尋,如果找到第一個非阿拉伯數字的位元組,顯然就是小木偶要找的。但是這種方法是有缺點的,最好還是由副程式提供要印出的位數較好。

接下來就是由最大位數開始,一位一位地把非聚集 BCD 數印在螢幕上。如果最大位數為零,就不需要印在螢幕上,第 53 行到第 59 行就是尋找不是零的最大位數。開始依次印出來,要注意的是在印出每一位數時必須加上 30H 使之成為 ASCII 碼。

ubcd_add 副程式

第 87 行到第 140 行是個副程式,這個副程式的主要工作就是把被加數的每一位數和加數的相對應位數相加,並存於 DX 暫存器所指位址的相對應位數。

也就是說首先取得被加數的個位數,再加上加數的個位數,並將結果存於和的個位數。小木偶用三個暫存器來表示被加數、加數以及和的位址,這三個暫存器分別是 AX、BX、DX,因為個位數在最低位址,故一開始運算時將這三個暫存器指向這三個 BCD 數的最低位址,待個位數相加完畢,將這三個暫存器分別加一,取得十位數再相加,重複此步驟就可以完成大部分工作。

未考慮將來最為程式庫的元件之一,故小木偶將它考慮得較複雜些。加法不一定是相同位數相加,也可能是不同位數相加。當不同位數相加時,小木偶分成三部份,重疊部份 (紫色部份,就是被加數與加數都有的部份)、非重疊部份 (橘色部份,就是只有較大位數有的部份) 及進位部份。請參考下面的加法:

  被加數:          9 3 5 0 4
    加數:    +           8 9
  ----------------------------
      和:          9 3 5 9 3

所以 ubcd_add 副程式一開始就是先求出被加數和加數那一個位數大?小木偶先假定被加數位數大,所以如果 CH>CL 那就直接到 106 行,否則交換被加數與加數之位數與最低位址 (第 102 行、第 103 行),反正加法是有『交換律』的。

再來就是計算重疊部份了 (第 111 行到第 120 行),這兒有個新指令:ADC。

新的指令:ADC

在 80X86 指令集中有一個考慮進位的加法指令,就是 ADC 指令。這個指令的語法如下:

ADC     目的,來源

其中目的及來源可以是暫存器、記憶體或是常數,但不能同時為記憶體即可,且要注意資料長度要相同。

ADD 與 ADC 都是在做加法運算時,他們兩個不同之處是 ADD 只單純將來源加到目的就好了,而 ADC 除了將來源加到目的之外,還會加上進位旗標作為最後結果存入目的中。

這兩個指令除了相加外還會設定進位旗標,因為這兩個指令都是針對十六進位設計的,所以如果相加之後的和超過或等於 100H 或 10000H 就會發生進位。而 AAA 也會設定進位旗標,只是 AAA 指令是針對十進位的運算,所以如果超過或等於 10D,就會發生進位。不管是那一種指令產生的進位狀態,當進位發生時,進位旗標都會被設定成『一』,在 DEBUG 顯示為 CY;如果沒發生進位,進位旗標就會被清除為『零』,在 DEBUG 顯示為 NC。

再回來看看程式。當個位數相加後,如果經調整後超過十,表示進位發生,第 30 行的 AAA 指令會設定進位旗標為一,若沒進位則進位旗標設為零。好,我們用 DEBUG 觀察看看。

H:\HomePage\SOURCE>debug bcd_add.com [Enter]
-g 18a [Enter]
93504+61689=
AX=0000  BX=01B5  CX=0005  DX=01BA  SP=FFF8  BP=0000  SI=01B0  DI=01BA
DS=1C8E  ES=1C8E  SS=1C8E  CS=1C8E  IP=018A   NV UP EI PL ZR NA PE NC
1C8E:018A 8A07          MOV     AL,[BX]                            DS:01B5=09
-t [Enter]

AX=0009  BX=01B5  CX=0005  DX=01BA  SP=FFF8  BP=0000  SI=01B0  DI=01BA
DS=1C8E  ES=1C8E  SS=1C8E  CS=1C8E  IP=018C   NV UP EI PL ZR NA PE NC
1C8E:018C 1204          ADC     AL,[SI]                            DS:01B0=04
-t [Enter]

AX=000D  BX=01B5  CX=0005  DX=01BA  SP=FFF8  BP=0000  SI=01B0  DI=01BA
DS=1C8E  ES=1C8E  SS=1C8E  CS=1C8E  IP=018E   NV UP EI PL NZ NA PO NC
1C8E:018E 37            AAA

因為 ADC 相加後並沒有超過 10H 故沒有進位(見紅色字)。再繼續追蹤。

-t [Enter]

AX=0103  BX=01B5  CX=0005  DX=01BA  SP=FFF8  BP=0000  SI=01B0  DI=01BA
DS=1C8E  ES=1C8E  SS=1C8E  CS=1C8E  IP=018F   NV UP EI PL NZ AC PO CY
1C8E:018F 46            INC     SI

看到上面進位旗標變成『CY』了嗎?這是因為做 AAA 調整後,發生了進位。幸好 80X86 指令中的 MOV、DEC、LOOP 指令都不會影響進位旗標,故可以保存這種進位或不進位的狀態到下次加法,這也就是我們可以利用這個性質依次做個位數、十位數、百位數……加法的理由。接下來 1C8E:0143 位址的指令是把每次做完加法後的結果存入 sum 變數中。

-t [Enter]

AX=0103  BX=01B5  CX=0005  DX=01BA  SP=FFF8  BP=0000  SI=01B1  DI=01BA
DS=1C8E  ES=1C8E  SS=1C8E  CS=1C8E  IP=0190   NV UP EI PL NZ NA PE CY
1C8E:0190 8805          MOV     [DI],AL                            DS:01BA=00
-t [Enter]

AX=0103  BX=01B5  CX=0005  DX=01BA  SP=FFF8  BP=0000  SI=01B1  DI=01BA
DS=1C8E  ES=1C8E  SS=1C8E  CS=1C8E  IP=0192   NV UP EI PL NZ NA PE CY
1C8E:0192 43            INC     BX
-t [Enter]

AX=0103  BX=01B6  CX=0005  DX=01BA  SP=FFF8  BP=0000  SI=01B1  DI=01BA
DS=1C8E  ES=1C8E  SS=1C8E  CS=1C8E  IP=0193   NV UP EI PL NZ NA PO CY
1C8E:0193 47            INC     DI
-t [Enter]

AX=0103  BX=01B6  CX=0005  DX=01BA  SP=FFF8  BP=0000  SI=01B1  DI=01BB
DS=1C8E  ES=1C8E  SS=1C8E  CS=1C8E  IP=0194   NV UP EI PL NZ NA PE CY
1C8E:0194 FEC9          DEC     CL
-t [Enter]

AX=0103  BX=01B6  CX=0004  DX=01BA  SP=FFF8  BP=0000  SI=01B1  DI=01BB
DS=1C8E  ES=1C8E  SS=1C8E  CS=1C8E  IP=0196   NV UP EI PL NZ NA PO CY
1C8E:0196 75F2          JNZ     018A
-t [Enter]

AX=0103  BX=01B6  CX=0004  DX=01BA  SP=FFF8  BP=0000  SI=01B1  DI=01BB
DS=1C8E  ES=1C8E  SS=1C8E  CS=1C8E  IP=018A   NV UP EI PL NZ NA PO CY
1C8E:018A 8A07          MOV     AL,[BX]                            DS:01B6=08
-t [Enter]

AX=0108  BX=01B6  CX=0004  DX=01BA  SP=FFF8  BP=0000  SI=01B1  DI=01BB
DS=1C8E  ES=1C8E  SS=1C8E  CS=1C8E  IP=018C   NV UP EI PL NZ NA PO CY
1C8E:018C 1204          ADC     AL,[SI]                            DS:01B1=00
-t [Enter]

AX=0109  BX=01B6  CX=0004  DX=01BA  SP=FFF8  BP=0000  SI=01B1  DI=01BB
DS=1C8E  ES=1C8E  SS=1C8E  CS=1C8E  IP=018E   NV UP EI PL NZ NA PE NC
1C8E:018E 37            AAA

注意看白色的部份,AL 是不是等於 8 加零再加上進位旗標?再注意,上述從位址 018a 到 0196 這幾行的指令在作用完之後,均不影響進位旗標。

-t

AX=0309  BX=010B  CX=0006  DX=003D  SP=FFFE  BP=0000  SI=0104  DI=0112
DS=1C8E  ES=1C8E  SS=1C8E  CS=1C8E  IP=0143   NV UP EI PL NZ NA PE NC
1C8E:0143 8805          MOV     [DI],AL                            DS:0112=00

這次調整之後,沒有進位吧?以後都是重複性的工作了,就不再贅述。

接下來是處理非重疊部份,這一部份和重疊部份其實幾乎一樣,只是第 124 行處是加上零而已。也不再贅述。而處理進位的這一部份,小木偶也不再重複了。

新的指令:CLC

這個指令很簡單,就是將進位旗標設成零。他沒有運算元。在 BCD_ADD.ASM 程式中,它的功用就是先把進位旗標歸零,免得往後做 ADC 運算時產生錯誤。


把 ubcd_print、ubcd_add 加入程式庫

可以預見,在這章所撰寫的兩個副程式 ubcd_print 和 ubcd_add 是很有用的,可為許多程式所呼叫,所以小木偶將這兩個副程式獨立出來,分別稱為 UBCD_PNT.ASM 和 UBCD_ADD.ASM。

先將 BCD_ADD.ASM 中的第 36 行到第 84 行標示並拷貝起來,然後開啟記事本,貼上,並在程式最前面及最後面加上幾行,使成下面的樣子:

code    segment
        assume  cs:code,ds:code
        public  ubcd_print
;---------------------------------------
;在螢幕上印出非聚集 BCD 數      ;37
………………
;中間省略
………………
ubcd_print      endp            ;85
;---------------------------------------
code    ends
;***************************************
        end     ubcd_print

然後存成 UBCD_PNT.ASM,同理也使 ubcd_add 副程式變成一可組譯的 UBCD_ADD.ASM,然後開啟 DOS 模式,執行下面步驟,使這兩個副程式加入第十章所建立的程式庫:

H:\HomePage\SOURCE>..\masm50\masm ubcd_pnt; [Enter]   組譯 UBCD_PNT.ASM
Microsoft (R) Macro Assembler Version 5.00
Copyright (C) Microsoft Corp 1981-1985, 1987.  All rights reserved.


  51452 + 381924 Bytes symbol space free

      0 Warning Errors
      0 Severe  Errors

H:\HomePage\SOURCE>..\masm50\masm ubcd_add; [Enter]   組譯 UBCD_ADD.ASM
Microsoft (R) Macro Assembler Version 5.00
Copyright (C) Microsoft Corp 1981-1985, 1987.  All rights reserved.


  51490 + 381886 Bytes symbol space free

      0 Warning Errors
      0 Severe  Errors

H:\HomePage\SOURCE>..\masm50\lib [Enter]   使UBCD_PNT.ASM 內的 ubcd_print
                                           加入 MYASMLIB.LIB 程式庫
Microsoft (R) Library Manager  Version 3.07
Copyright (C) Microsoft Corp 1983-1987.  All rights reserved.

Library name: myasmlib [Enter]
Operations: +ubcd_pnt [Enter]
List file:  [Enter]
Output library: myasmlib [Enter]

H:\HomePage\SOURCE>..\masm50\lib [Enter]   使UBCD_ADD.ASM 內的 ubcd_add
                                           加入 MYASMLIB.LIB 程式庫
Microsoft (R) Library Manager  Version 3.07
Copyright (C) Microsoft Corp 1983-1987.  All rights reserved.

Library name: myasmlib [Enter]
Operations: +ubcd_add [Enter]
List file: myasmlib.lst [Enter]
Output library: myasmlib [Enter]
H:\HomePage\SOURCE>type myasmlib.lst [Enter]
PRINT_BL..........pnt_bl            PRINT_BX..........pnt_bx
UBCD_ADD..........ubcd_add          UBCD_PRINT........ubcd_pnt


pnt_bl            Offset: 00000010H  Code and data size: 11eH
  PRINT_BL

pnt_bx            Offset: 000000a0H  Code and data size: 128H
  PRINT_BX

ubcd_pnt          Offset: 00000140H  Code and data size: 3bH
  UBCD_PRINT

ubcd_add          Offset: 000001e0H  Code and data size: 39H

這樣小木偶就有四個副程式了。


結論

這一章奡ㄗ BCD 數的概念,份量算是相當得多。但是我想最重要得是您是否能用自己的話說出來何謂 BCD 數,假如您能夠的話,相信您真的懂了,假如您不能的話,建議您用 DEBUG 去追蹤看看。至於上述介紹許多新的指令其實都是末節,如果您真的已經知道 BCD 數的意義,這些指令您自然而然的就已經能記得了,即使像小木偶一樣記不太清楚,到時查查書,幾分鐘內還是能寫出來。

至於您說,小木偶不是要寫出很大位數的加法嗎?怎麼只有五位數呢?其實很簡單,假如您想計算 20 位數,您只需要把 BCD_ADD.ASM 第一行的 digit 改成 20,然後把 v1、v2 改成您要相加的兩數(要記得每一個數都要有 20 位數),再重新編譯連結即可。

回到首頁到第十二章到第十四章