Wilco Breedt
Wilco Breedt

Reputation: 23

Getting Incorrect answer on Assembly Language Loop (Assembly 8086)

I'm new to assembly language and I'm struggling to find out what is going on with this program I am writing . The goal of the program is for the user to enter a number between 0 and 9 . It then checks if this number is a multiple of 3 , if an incorrect character (i.e Z ) is entered it should simply put '**' next to the character and ask again for the user to enter a number. If a number is entered it is then converted from a ASCII character to a number and then divided .

Now the problem is when I enter a correct character(0-9) first the answer comes back correctly . Lets say I enter 3 it will say its a multiple , if I enter 4 first it will say its not a multiple which is correct . But as soon as I enter a incorrect character and it starts looping it doesn't give me the correct answer once I enter in a correct character . Please look at this code and see if anyone can help me .

HERE IS THE CODE:

    bits 16 
    org 0x100
    jmp main

    name db 'Assembly Program Divider','$',0dh,0ah
    prompt db 'Please enter a number (0 - 9): ','$',0dh,0ah
    invalid db 'Invalid Character','$',0dh,0ah
    is db 'Number is a multiple of 3','$',0dh,0ah
    isnot db 'Number is not a multiple of 3','$',0dh,0ah
    asteriks db '**','$',0dh,0ah

    cr_lf: 
    db 0dh,0ah,'$' ; carriage return and line feed

;Scroll and clear the screen and also change background and text color
    clr_scr:
    mov ax,0600h ; Scroll the entire screen
    mov bh,1Eh ; Colour: white to blue
    mov cx,00 ; From top left corner
    mov dx,184Fh ; To lower right corner
    int 10h ; BIOS interrupt
    nop
    ret

;Set the cursor position on the screen
    set_csr:
    mov ah,02 ; Set cursor
    mov bh,00 ; Screen number 0
    mov dx,0a00h ; row 10, column 0
    int 10h ; BIOS interrupt
    ret

;Display a message on the terminal
    disp_str:
    mov ah,09 ; Display message
    int 21h ; DOS interrupt
    ret

;Read a character from terminal
    read_chr:
    mov dx,cr_lf ; address of line feed
    call disp_str ; display line feed
    mov dx,prompt ; address of prompt
    call disp_str ; display prompt
    mov ah,01 ; get character from keyboard
    int 21h ; DOS system call
    cmp al,39h
    jg diss_invalid
    cmp al,30h
    jl diss_invalid
    ret

;Display a message for an invalid character entered
    diss_invalid:
    mov dx,asteriks ;address of asteriks to display next to character
    call disp_str ; display asteriks 
    call read_chr ; run the read character again untill correct character is entered

;Display message for when the number is a multiple of 3
    diss_is:
    mov dx,cr_lf ; address of line feed
    call disp_str ; display line feed
    mov dx,is
    mov ah,09 ; Display message
    int 21h ; DOS interrupt
    int 20h ; Terminate Program

;Display message for when the number is not a multiple of 3
    diss_isnot:
    mov dx,cr_lf ; address of line feed
    call disp_str ; display line feed
    mov dx,isnot
    mov ah,09 ; Display message
    int 21h ; DOS interrupt
    int 20h ; Terminate program

    str_to_num:
    mov dx, 0
    sub al,0    ; ASCII value converted to numeric value
    mov ah,0     ; AX = numeric value of digit
    add dx,dx    ; DX = 2 * original DX
    add ax,dx    ; AX = 2 * original DX + digit
    add dx,dx    ; DX = 4 * original DX
    add dx,dx    ; DX = 8 * original DX
    add dx,ax    ; DX = 10 * original DX + digit
    ret

    divide:
    mov ah,0 ; clear AH 
    mov dl,03h ; Divisor 3 in DL
    idiv dl ; integer division , remainder stored in AH
    cmp ah,0 ; compare the remainder to 0
    je diss_is ; if the reminder = 0 go to display is
    cmp ah,0 ; compare the remainder to 0
    jg diss_isnot ; if the reminder is not 0 go to display isnot

;Main program to be executed
    main:
    call clr_scr ; clear screen
    call set_csr ; set cursor
    mov dx,name ; address of name and student number
    call disp_str ; display name and student number
    call read_chr ; read character
    call str_to_num ; convert the character received to a number
    call divide ; divide the converted number by 3 and check if it has a remainder
    int 20h ; Terminate the program if all is successfull

Thanks in advance !!

Here is examples of what happens:

Please enter a number (0 - 9)j**
Please enter a number (0 - 9)k**
Please enter a number (0 - 9)7
Number is a multiple of 3

Please enter a number (0 - 9)j**
Please enter a number (0 - 9)k**
Please enter a number (0 - 9)8
Number is a multiple of 3

Please enter a number (0 - 9)j**
Please enter a number (0 - 9)k**
Please enter a number (0 - 9)3
Number is a multiple of 3

Please enter a number (0 - 9)7
Number is not a multiple of 3

Please enter a number (0 - 9)3
Number is a multiple of 3

Upvotes: 2

Views: 1130

Answers (2)

Ped7g
Ped7g

Reputation: 16626

I will add to this answer based on your responses in comments...

First about the:

divide:
mov ah,0
mov dl,03h
idiv dl
cmp ah,0
je diss_is
cmp ah,0
jg diss_isnot

it divides the number in ax (dividend) with the divisor in dl (where I moved the hex number 3 into) the remainder is then stored in ah . I then compare ah with 0 , when it is 0 there is no remainder and it must be a multiple of 3 .

Quite good, but let's nitpick, because CPU does too (well, it just follows your instructions literally, not caring about nitpicking, but it feels sometimes like it is).

"the hex number 3 into" - the number 3 is number 3. We have many possible forms how to express it, from common decimal format 3 used by many humans, trough similar hexadecimal format 03h (in some assemblers also C-like 0x03 works, or even $03), binary 0b11, octal 0q03 or 03, trough less numeric ways, like Roman numbers III, or just some expression like *** (count of stars). In the CPU I prefer to imagine it as 8 (16/32/..) wires, each having either current flowing trough it, or not. Each wire then represents single bit, composing the 8/16/32/.. bit number.

So it doesn't matter how you write that 3, if it's hexadecimal form 03h or decimal 3, it will set bits in dl as 00000011.

You can easily think about that "hexa number 3" as 3, there's no benefit to have it in hexa in this particular case, actually as the task description was about division by 3, I would rather stick to decimal form even in the source code. You will get the feel, where hexadecimal form is simpler, over time.

You then compare the remainder with zero, when equal, you jump to diss_is. That's completely correct.

But that's not all of the code, and it's not covering all possible cases. When the remainder is not equal to zero (it's 1 or 2), what will happen? After cmp ah,0 the flags will be set up, in the same way, as after sub ah,0. Both 1-0 and 2-0 will set the interesting flags in the same way: ZF=0, CF=0, SF=0, OF=0.

So je diss_is will not branch, and the execution will continue on the next cmp ah,0. But did the je change some flags? It didn't. So it's not needed to do the cmp again, you already have the result of it in the flags from the first one.

Then you do jg diss_isnot, which will branch for both 1 and 2, but as you covered the "0 remainder" case with je, you can just jmp diss_isnot directly without any condition. Or move the code from diss_isnot under the je.

If there would be some additional code changing flags ahead of jg, making it possible to not branch, the code would re-enter the main by accident! Can't happen in your case, but don't write code in this way, use simpy jmp to avoid any confusion.

Actually you do call divide in the main, but you will never return from it, because both branches have their own int 20h. You could shorten your code a bit by sharing removing duplicities in code, like calling the cr_lf before test (just make sure you store al with number somewhere, stack should work well), or even displaying the result by the same code, just adjusting the dx,<result_message_offset> in the branch, and then using the same code to end divide. And ret from it, so exit is only in main.

... end of part 1 ...

Now look at that sub al,0 ASCII conversion.

And finally check your code flow for non number char, how it will branch, and what will happen on stack. Keep commenting back, what you think the code does.

Upvotes: 1

cure
cure

Reputation: 2688

So this could be wrong because I didn't test it but I’m thinking at the end of diss_invalid you call read_chr right? And when it returns it returns to the next instruction after it was called back under diss_invalid.... which isn’t what you meant right? So instead of calling read_chr, I would put a label in main after call disp_str ; display name and student number that you can jump to from diss_invalid. But that is just a temporary solution. A more permanent one would be to start commenting EVERYTHING and to get out a piece of paper and draw out what you think should happen, then check if the code you wrote does that. Try to modularize everything. Should read char really be sanitizing input or should that be a separate function? etc...

Upvotes: 1

Related Questions