Search code examples
assemblyx86x86-16masm

How do I display the number of equal and different characters between two strings in assembly?


The program will ask for 2 strings (with a maximum size of 150 characters, for example).

INPUT

String 1: "Hello this is the first string"

String 2: "Woops this string is different and longer!"

OUTPUT

Number of equal characters = 26
Number of different characters = 15

I have in mind that we don't have to "calculate" the different characters as we can get the size of the longest string and subtract the number of equal characters but I don't know how to do this first comparison (the number of equal characters).

How can I do this? Could I do a macro for that comparison?

UPDATE

I am using EMU8086, MASM. I don't want to use string instructions.

This is my current code, following the instructions in the comments, but I still can't make it work.

.model small
.stack 100h
.data
    Text1 DB "Please enter the first phrase: ",13,10,'$'
    Text2 DB "Please enter the second phrase: ",13,10,'$'
    
    max1 DB 151 ;We add one to 150 for the ENTER
    charRead1 DB 0       
    
    max2 DB 151 ;We add one to 150 for the ENTER
    charRead2 DB 0
    
    TextoEquals DB "Number of equal characters: ",13,10,'$'
    TextoDiffer DB "Number of different characters: ",13,10,'$'
     
    linefeed DB 13, 10, "$" 
    size1 dw 0000h
    size2 dw 0000h
.code

AllMacros:
        
    MagicFunction MACRO
        cld                     ; Clear direction flag
        xor bx, bx              ; Reset counter
        mov si, OFFSET max1     ; Pointer to shorter string
        mov dx, size1           ; Length of the shorter string
        NextChar: 
            lodsb                   ; Fetch next character from shorter string
            mov di, offset max2     ; Pointer to longer string
            mov cx, size2              ; Length of the longer string
            repne 
            scasb
            jne NotFound 
            inc bx                  ; Increment counter
            mov byte ptr [di-1], 0  ; Strike by replacing with zero   
            
            NotFound:
                dec dx               ; Repeat for all characters in shorter string
                jnz NextChar   
    ENDM
    
    
    PrintText MACRO str
        MOV AH, 09h
        LEA DX, str
        INT 21h
    ENDM 
    
    PrintCls MACRO
        mov ah, 09h
        mov dx, offset linefeed
        int 21h 
    ENDM


Inicio:
    mov ax, @data
    mov ds, ax
    
    PrintText Text1
    
    mov ah, 0Ah        
    lea dx, max1 
    mov size1, offset max1
    int 21h
                           
    PrintCls  
    
    PrintText Text2
    
    mov ah, 0Ah        
    lea dx, max2 
    mov size2, offset max2
    int 21h  
    
    PrintCls  
    
    MagicFunction
    
    PrintText TextoEquals
    
    mov ah, 9
    lea dx, bx
    int 21h
     
    PrintCls
    
    PrintText TextoDiffer
    
    mov ah, 9
    lea dx, dx
    int 21h
    
END Inicio 

Solution

  • Thank you for including code in your question and tagging it appropriately. It allows me to write a full review this time. Mind you, the other answer is not a half answer as you seem to imply. That answer is a par with your question at the time.


    How can I do this? Could I do a macro for that comparison?

    The code that I already provided in my previous answer, and that you have adopted, is not really meant to be used as a macro. If anything, it could become a subroutine, but I see no benefit from doing that in your program. Just inline the code.

    Inicio:
        mov ax, @data
        mov ds, ax
    

    As I wrote in my previous answer, the scasb instruction uses the ES segment register. You should set it up the same as the DS segment register:

    Inicio:
        mov ax, @data
        mov ds, ax
        mov es, ax
    

    Inputting

    How buffered input works explains in great detail how the DOS.BufferedInput function 0Ah works.
    Where you wrote:

    max1 DB 151 ;We add one to 150 for the ENTER
    charRead1 DB 0       
    

    you forgot to reserve room for the actual characters that you would like to receive from the user. Next is how it needs to be:

    max1      DB 151
    charRead1 DB 0       
    chars1    DB 151 dup(0)
    

    When you invoke this DOS function 0Ah, you will also receive in the charRead1 byte the length of the actual input. That is the value that you need to store in your size1 variable (not the address of the input structure like your code is doing now!):

    mov ah, 0Ah        
    lea dx, max1 
    mov size1, offset max1   <<<< is wrong
    int 21h
    

    And of course, you can only do this after the DOS call was made:

    mov  dx, offset max1  ; Address of the input structure
    mov  ah, 0Ah
    int  21h
    mov  al, charRead1    ; Length of the string
    mov  ah, 0            ; Extending is needed because size1 is a word-sized variable
    mov  size1, ax
    

    Same goes for the second string.

    Outputting

    Displaying numbers with DOS explains in great detail how you can print the value from the 16-bit register AX.

    mov ah, 9
    lea dx, bx
    int 21h
    

    When you use the DOS.PrintString function 09h, DOS expects in DX a pointer to a string of characters. The BX register in your code holds a number and you still need to convert that into a string of digits. The 'method 2' snippet from the link does that and also displays the characters immediately using the DOS.PrintCharacter function 02h. Displaying characters with DOS or BIOS has info about many output functions. You only need to insert the code snippet at the ellipses.

    mov  ax, bx
    push bx             ;(0)
    ...
    pop  bx             ;(0)
    

    And since this is my original code, I'll do the copying for you...

        mov     ax,bx
        push    bx             ;(0)
        mov     bx,10          ;CONST
        xor     cx,cx          ;Reset counter
    .a: xor     dx,dx          ;Setup for division DX:AX / BX
        div     bx             ; -> AX is Quotient, Remainder DX=[0,9]
        push    dx             ;(1) Save remainder for now
        inc     cx             ;One more digit
        test    ax,ax          ;Is quotient zero?
        jnz     .a             ;No, use as next dividend
    .b: pop     dx             ;(1)
        add     dl,"0"         ;Turn into character [0,9] -> ["0","9"]
        mov     ah,02h         ;DOS.DisplayCharacter
        int     21h            ; -> AL
        loop    .b
        pop     bx             ;(0)
    

    (0) Preserving BX is necessary because you need the value a second time to display the number of different characters.
    You knew that you didn't have to "calculate" the different characters as you could get the size of the longest string and subtract the number of equal characters. But why didn't you do that subtraction in your program?

        mov     ax,size2       ;SizeLongestString
        sub     ax,bx          ; minus EqualCharacters
    
        mov     bx,10          ;CONST
        xor     cx,cx          ;Reset counter
    .a: xor     dx,dx          ;Setup for division DX:AX / BX
        div     bx             ; -> AX is Quotient, Remainder DX=[0,9]
        push    dx             ;(1) Save remainder for now
        inc     cx             ;One more digit
        test    ax,ax          ;Is quotient zero?
        jnz     .a             ;No, use as next dividend
    .b: pop     dx             ;(1)
        add     dl,"0"         ;Turn into character [0,9] -> ["0","9"]
        mov     ah,02h         ;DOS.DisplayCharacter
        int     21h            ; -> AL
        loop    .b
    

    So it seems that we have inserted twice the code to convert and display a number. That's a good reason to put it in a subroutine and then just call it twice. See below.


    Putting everything together we get the following program made for emu8086 (MASM)

    .model small
    .stack 100h
    .data
        Text1       DB "Please enter the shorter phrase: ",13,10,'$'
        Text2       DB "Please enter the longer phrase: ",13,10,'$'
        
        max1        DB 151
        charRead1   DB 0       
        chars1      DB 151 dup(0)
    
        max2        DB 151
        charRead2   DB 0       
        chars2      DB 151 dup(0)
        
        TextoEquals DB "Number of equal characters: ",13,10,'$'
        TextoDiffer DB "Number of different characters: ",13,10,'$'
         
        linefeed    DB 13, 10, "$" 
        size1       DW 0
        size2       DW 0
    
    .code
    AllMacros:
        PrintText MACRO str
            MOV AH, 09h
            LEA DX, str
            INT 21h
        ENDM 
        
        PrintCls MACRO
            mov ah, 09h
            mov dx, offset linefeed
            int 21h 
        ENDM
    
    Inicio:
      mov  ax, @data
      mov  ds, ax
      mov  es, ax
        
      PrintText Text1
        
      mov  dx, offset max1
      mov  ah, 0Ah
      int  21h
      mov  al, charRead1
      mov  ah, 0
      mov  size1, ax
    
      PrintCls  
      PrintText Text2
        
      mov  dx, offset max2
      mov  ah, 0Ah
      int  21h
      mov  al, charRead2
      mov  ah, 0
      mov  size2, ax
    
      PrintCls  
    
      cld                     ; Clear direction flag
      xor  bx, bx             ; Reset counter
      mov  si, offset chars1  ; Pointer to shorter string
      mov  dx, size1          ; Length of the shorter string
    NextChar:
      lodsb                   ; Fetch next character from shorter string
      mov  di, offset chars2  ; Pointer to longer string
      mov  cx, size2          ; Length of the longer string
      repne scasb
      jne  NotFound 
      inc  bx                 ; Increment counter
      mov  byte ptr [di-1], 0 ; Strike by replacing with zero
    NotFound:
      dec  dx                 ; Repeat for all characters in shorter string
      jnz  NextChar
    
      PrintText TextoEquals
        
      mov  ax, bx
      call DisplayAX
    
      PrintCls
      PrintText TextoDiffer
        
      mov  ax, size2
      sub  ax, bx
      call DisplayAX
    
      mov  ax, 4C00h         ; DOS.Terminate
      int  21h
        
    DisplayAX:
        push    bx             ;(0)
        mov     bx,10          ;CONST
        xor     cx,cx          ;Reset counter
    .a: xor     dx,dx          ;Setup for division DX:AX / BX
        div     bx             ; -> AX is Quotient, Remainder DX=[0,9]
        push    dx             ;(1) Save remainder for now
        inc     cx             ;One more digit
        test    ax,ax          ;Is quotient zero?
        jnz     .a             ;No, use as next dividend
    .b: pop     dx             ;(1)
        add     dl,"0"         ;Turn into character [0,9] -> ["0","9"]
        mov     ah,02h         ;DOS.DisplayCharacter
        int     21h            ; -> AL
        loop    .b
        pop     bx             ;(0)
        ret
    
    END Inicio 
    

    Never forget to exit your program properly. For DOS, the preferred way is to use the DOS.Terminate function 4Ch.


    [Recent addition to the question]

    I don't want to use string instructions

    That's ok for me. Simply replacing the lodsb and repne scasb string instructions with equivalent code:

      xor  bx, bx             ; Reset counter
      mov  si, offset chars1  ; Pointer to shorter string
      mov  dx, size1          ; Length of the shorter string
    NextChar:
      mov  al, [si]           ; Fetch next character from shorter string
      inc  si
      mov  di, offset chars2  ; Pointer to longer string
      mov  cx, size2          ; Length of the longer string
    Scan:
      cmp  al, [di]
      je   Found
      inc  di
      loop Scan
      jmp  NotFound
    Found:
      inc  bx                 ; Increment counter
      mov  byte ptr [di], 0   ; Strike by replacing with zero
    NotFound:
      dec  dx                 ; Repeat for all characters in shorter string
      jnz  NextChar
    

    Strictly speaking, it's no longer necessary to set up ES, but it won't harm the program if you leave it in.