assemblyx86nasmx86-16interruption

How do I use the int 16h interrupt to store a number of more than one digit typed from the keyboard?


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.


Solution

  • 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