I am implementing a fractional number calculator (numerators and denominators must be entered separately) for an assembly language course. I had already finished the logic of my program, but I realized that int 16h had to be used for reading data. I have this function:
ReadDecimalNumber16h proc
push ax
push bx
push cx
push dx
xor bx, bx ; BX = accumulator ← 0
.ReadKey:
mov ah, 00h
int 16h ; Read a key → AL
cmp al, 13 ; Inter?
je .EndRead ; Yes → we're done.
; Display echo of character
mov ah, 0Eh
int 10h
; Validate if digit '0'..'9'
cmp al, '0'
jb .LeeTecla ; Minor → ignore
cmp al, '9'
ja .LeeTecla ; Major → ignore.
; Convert character to number
sub al, '0' ; AL = numeric value (0-9)
xor ah, ah ; AH = 0 → AX = value
; BX = BX * 10 + AX
mov cx, 10
mul cx ; AX = AX * 10
add bx, ax ; BX = accumulator
jmp .ReadKey
.EndRead:
mov ax, bx ; End result → AX
pop dx
pop cx
pop bx
pop ax
ret
ReadDecimalNumber16h endp
But no matter how much echo tells me that I am pressing the correct keys, the decimal values are not saved correctly and only store zeros (0). I don't understand where my error is or if there is a better way to save these numbers for this interrupt. I would appreciate a hand.
User @clarkep already pinpointed the two main problems, but you might still not readily see how to actually solve it.
xor bx, bx ; BX = accumulator ← 0
Of course this is a nitpick, but using the x86 nomenclature AX, BX, CX, ... are 'general purpose registers', but only AX is additionally called 'accumulator'.
mov ah, 00h int 16h ; Read a key → AL
The BIOS.ReadKeystroke function returns in the full AX register. Eventhough in this particular case you are not interested in the scancode from AH, it is in your own interest to be as exact as possible in the comments that you write.
; Validate if digit '0'..'9' cmp al, '0' jb .LeeTecla ; Minor → ignore cmp al, '9' ja .LeeTecla ; Major → ignore.
The .LeeTecla is nowhere to be found in your proc! Trying my best on that mention 'ignore', I could assume that you either mean to jump to .ReadKey or .EndRead, but having the reader 'assume' is not a good programming practice!
; Display echo of character mov ah, 0Eh int 10h
The BIOS.Teletype function depends on the DisplayPage number in BH (and for the graphics modes also the CharacterColor in BL), but because you are building the result in BX (that is composed of BL and BH), there will come a moment when BH changes from 0 to some other value and thus potentially the echo will write on a non-displayed video page. My below solution uses CX for containing the result.
ReadDecimalNumber16h proc
push bx
push cx
push dx
xor cx, cx ; Value under construction
.ReadKey:
mov ah, 00h ; BIOS.ReadKeystroke
int 16h ; -> AX
cmp al, 13 ; <ENTER> ?
je .EndRead ; Yes → we're done.
; Display echo of character
mov bx, 0007h ; DisplayPage 0 and CharacterColor 7 (White)
mov ah, 0Eh ; BIOS.Teletype
int 10h
; Validate if digit '0'..'9' AND convert character to number
sub al, '0'
cmp al, 10
jnb .ReadKey ; Ignore.
cbw ; -> AX = [0,9]
; CX = CX * 10 + AX
xchg ax, cx
mov dx, 10
mul dx ; -> DX:AX = AX * 10
add cx, ax ; CX = current result
jmp .ReadKey
.EndRead:
mov ax, cx ; AX = Final result
pop dx
pop cx
pop bx
ret
ReadDecimalNumber16h endp