Search code examples
assemblyx86-16tasm

How can I multiply two large numbers in assembly and print out the result?


I'm new to assembly and I've been trying to create a program which will take three numbers ([numR], [numG], [numB]) with a maximum length of 3 digits for each numbers and then perform the formula: (65536*[numR] + 256*[numG] + [numB]) and finally print out the result.

I've already coded most of the program (I've been able to get the input from the user for each number and store it in the 3 variables shown above) but I just can't seem to find a way to perform the formula, mostly because when multiplying big numbers, the result gets separated between register dx and ax.

I'm not sure how may this help, but here is my code so far:

;------------------------------------------
; PURPOSE : Color Code Generator
; SYSTEM  : Turbo Assembler Ideal Mode  
; AUTHOR  : Re'em Kishnevsky
;------------------------------------------

    IDEAL

    MODEL small

    STACK 256

    DATASEG
    include 'start.txt'

    count db 0

    num db ?, ?, ?

    numR dw ?
    numG dw ?
    numB dw ?

    numFinal dd ?

    CODESEG
Start:
    mov ax, @data
    mov ds, ax

    ;Sets text mode
    mov ah, 0
    mov al, 2   ; 3
    int 10h


    ;Sets cursor position 0,0
    mov dh, 0
    mov dl, 0
    call SetCursorPosition

    ;Prints initial message
    mov dx, offset msg
    mov ah, 09h
    int 21h

    call ReadKeyInput

    ;Sets cursor position 0,0
    mov dh, 0
    mov dl, 0
    call SetCursorPosition

    ;Paints the screen red
    mov bl, 01000000b
    call PaintScreen

    ;Prints RED message
    mov bp, offset red
    mov bl, 01001111b
    mov cx, 31
    mov dh, 10
    mov dl, 24
    call PrintMessage

    ;Sets cursor position 35,12
    mov dh, 12
    mov dl, 35
    call SetCursorPosition

    mov bl, 01001111b
    call DetermineNumber

    cmp [count], 1
    je R1Digit
    cmp [count], 2
    je R2Digit

    dec si
    push [si]
    pop [numR]
    dec si
    mov al, 10
    mul [byte ptr si]
    add [numR], ax
    dec si
    mov al, 100
    mul [byte ptr si]
    add [numR], ax
    jmp Phase_green

R1Digit:
    dec si
    push [si]
    pop [numR]
    jmp Phase_green
R2Digit:
    dec si
    push [si]
    pop [numR]
    dec si
    mov al, 10
    mul [byte ptr si]
    add [numR], ax

Phase_green:
    ;Sets cursor position 0,0
    mov dh, 0
    mov dl, 0
    call SetCursorPosition

    ;Paints the screen green
    mov bl, 00100000b
    call PaintScreen

    ;Prints GREEN message
    mov bp, offset green
    mov bl, 00101111b
    mov cx, 33
    mov dh, 10
    mov dl, 24
    call PrintMessage

    ;Sets cursor position 35,12
    mov dh, 12
    mov dl, 35
    call SetCursorPosition
    mov [count], 0 
    mov bl, 00101111b
    call DetermineNumber

    cmp [count], 1
    je G1Digit
    cmp [count], 2
    je G2Digit

    dec si
    push [si]
    pop [numG]
    dec si
    mov al, 10
    mul [byte ptr si]
    add [numG], ax
    dec si
    mov al, 100
    mul [byte ptr si]
    add [numG], ax
    jmp Phase_blue

G1Digit:
    dec si
    push [si]
    pop [numG]
    jmp Phase_blue
G2Digit:
    dec si
    push [si]
    pop [numG]
    dec si
    mov al, 10
    mul [byte ptr si]
    add [numG], ax

Phase_blue:
    ;Sets cursor position 0,0
    mov dh, 0
    mov dl, 0
    call SetCursorPosition

    ;Paints the screen blue
    mov bl, 00010000b
    call PaintScreen

    ;Prints GREEN message
    mov bp, offset blue
    mov bl, 00011111b
    mov cx, 32
    mov dh, 10
    mov dl, 24
    call PrintMessage

    ;Sets cursor position 35,12
    mov dh, 12
    mov dl, 35
    call SetCursorPosition
    mov [count], 0 
    mov bl, 00011111b
    call DetermineNumber

    cmp [count], 1
    je B1Digit
    cmp [count], 2
    je B2Digit

    dec si
    push [si]
    pop [numB]
    dec si
    mov al, 10
    mul [byte ptr si]
    add [numB], ax
    dec si
    mov al, 100
    mul [byte ptr si]
    add [numB], ax
    jmp Phase_final

B1Digit:
    dec si
    push [si]
    pop [numB]
    jmp Phase_final
B2Digit:
    dec si
    push [si]
    pop [numB]
    dec si
    mov al, 10
    mul [byte ptr si]
    add [numB], ax

Phase_final:

    mov ax, 32768 ;This is where I want the formula calculation to be performed.
    mul [numR]    ;as you can see, I divided 65536 by two so it could fit in register ax


Exit:   

    mov ax, 4C00h
    int 21h

;-----------------------------------------
;DetermineNumber - Determines the number input from the user
;-----------------------------------------
;Input:
;       bl <- attribute of character
;Output:
;       [num] <- (digit 1, digit 2, digit 3) ,Written number 
;Registers:
;       ah, al, bh, bl, dh, dl, cx, si
;-----------------------------------------
Proc DetermineNumber
    mov si, offset num
@@Determine_number:

call ReadKeyInput

cmp al, 48
je @@0
cmp al, 49
je @@1
cmp al, 50
je @@2
cmp al, 51
je @@3
cmp al, 52
je @@Mid1_4
cmp al, 53
je @@Mid1_5
cmp al, 54
je @@Mid1_6
cmp al, 55
je @@Mid1_7
cmp al, 56
je @@Mid1_8
cmp al, 57
je @@Mid1_9
cmp al, 27
je @@Mid1_ESC
cmp al, 13
je @@Mid1_Enter

@@0: cmp [count], 3
 je @@Determine_number
 mov [byte ptr si], 0
 inc [count]
 inc si
 mov al, '0'
 call PrintCharacter
 jmp @@Determine_number

@@1: cmp [count], 3
 je @@Determine_number
 mov [byte ptr si], 1
 inc [count]
 inc si
 mov al, '1'
 call PrintCharacter
 jmp @@Determine_number

@@2: cmp [count], 3
 je @@Determine_number
 mov [byte ptr si], 2
 inc [count]
 inc si
 mov al, '2'
 call PrintCharacter
 jmp @@Determine_number



@@3: cmp [count], 3
 je @@Determine_number
 mov [byte ptr si], 3
 inc [count]
 inc si
 mov al, '3'
 call PrintCharacter
 jmp @@Determine_number

@@Mid1_Determine_number: jmp @@Determine_number
@@Mid1_4: jmp @@4
@@Mid1_5: jmp @@5
@@Mid1_6: jmp @@6
@@Mid1_7: jmp @@Mid2_7
@@Mid1_8: jmp @@Mid2_8
@@Mid1_9: jmp @@Mid2_9
@@Mid1_ESC: jmp @@Mid2_ESC
@@Mid1_Enter: jmp @@Mid2_Enter

@@4: cmp [count], 3
 je @@Mid1_Determine_number
 mov [byte ptr si], 4
 inc [count]
 inc si
 mov al, '4'
 call PrintCharacter
 jmp @@Mid1_Determine_number


@@5: cmp [count], 3
 je @@Mid1_Determine_number
 mov [byte ptr si], 5
 inc [count]
 inc si
 mov al, '5'
 call PrintCharacter
 jmp @@Mid1_Determine_number

@@6: cmp [count], 3
 je @@Mid1_Determine_number
 mov [byte ptr si], 6
 inc [count]
 inc si
 mov al, '6'
 call PrintCharacter
 jmp @@Mid1_Determine_number

@@Mid2_Determine_number: jmp @@Determine_number
@@Mid2_5: jmp @@5
@@Mid2_6: jmp @@6
@@Mid2_7: jmp @@7
@@Mid2_8: jmp @@8
@@Mid2_9: jmp @@9
@@Mid2_ESC: jmp @@ESC
@@Mid2_Enter: jmp @@Enter

@@7: cmp [count], 3
 je @@Mid2_Determine_number
 mov [byte ptr si], 7
 inc [count]
 inc si
 mov al, '7'
 call PrintCharacter
 jmp @@Mid2_Determine_number

@@8: cmp [count], 3
 je @@Mid2_Determine_number
 mov [byte ptr si], 8
 inc [count]
 inc si
 mov al, '8'
 call PrintCharacter
 jmp @@Mid2_Determine_number

@@9: cmp [count], 3
 je @@Mid2_Determine_number
 mov [byte ptr si], 9
 inc [count]
 inc si
 mov al, '9'
 call PrintCharacter
 jmp @@Mid2_Determine_number

@@ESC: call EndProgram

@@Enter: 
    cmp [count], 0
    je @@Mid2_Determine_number

ret
ENDP DetermineNumber
;-----------------------------------------
;ReadKeyInput - Reads key input
;-----------------------------------------
;Input:
;       Keyboard key press
;Output:
;       ah <- scan code, al <- ascii code
;Registers:
;       ah, al
;-----------------------------------------
Proc ReadKeyInput
    mov ah, 00h
    int 16h
    ret
ENDP ReadKeyInput

;----------------------------------------------------------------
;PaintScreen - Paints the screen in a specific color
;----------------------------------------------------------------
;Input:
;       bl -> color
;Output:
;       Printed message
;Registers:
;       ah, al, bh, bl, cx
;----------------------------------------------------------------
PROC PaintScreen
    mov ah, 09h 
    mov bh, 0    ; page number 
    mov cx, 2000     ; count of characters to write
    mov al, ''   ; character to write
    int 10h

    ret
ENDP PaintScreen

;----------------------------------------------------------------
;PrintMessage - Prints a message
;----------------------------------------------------------------
;Input:
;       bp -> offset of message, bl -> attribute, dl -> Starting column, dh -> Starting row, cx -> length
;Output:
;       Printed message
;Registers:
;       ah, al, bh, cx, dx, es, bp
;----------------------------------------------------------------
PROC PrintMessage
    mov ah, 13h     ; video page number
    mov bh, 0
    mov al, 0       ; 0-3 indicating mode
    push ds
    pop es      ; es:bp pointer to string to be written
    int 10h
ret
ENDP PrintMessage

Proc EndProgram
mov dh, 0
mov dl, 0
call SetCursorPosition

mov bl, 0Fh
call PaintScreen
mov ax, 4C00h
int 21h
ret
ENDP EndProgram

;----------------------------------------------------------------
;SetCursorPosition - Sets Cursor Position
;----------------------------------------------------------------
;Input:
;       dl -> Column, dh -> Row
;Output:
;       Printed message
;Registers:
;       ah, bh, dh, dl
;----------------------------------------------------------------
Proc SetCursorPosition
    mov bh, 0   
    mov ah, 2h  
    int 10h
ret
ENDP SetCursorPosition

Proc PrintCharacter
    mov ah, 09h 
    mov bh, 0    ; page number 
    mov cx, 1    ; count of characters to write
    int 10h

    inc dl
    call SetCursorPosition

ret
ENDP PrintCharacter

    END start

Here is the content of start.txt:

msg db "Press any key to continue"
red   db   "Please type in the value of RED"

green     db   "Please type in the value of GREEN"

blue      db   "Please type in the value of BLUE"

final     db   "Your color code is $"

Processor: Intel 8086, Assembler: TASM


Solution

  • You should probably stop and realize why those RGB 24 bit colours are used like that by some weird formula 65536*R ...

    As every value in computer, the colours are encoded in bits too. In 24b RGB (32b ARGB) format each colour channel has 8 bits (1 byte). 256 = 28, and 65536 = 216 (1 = 20 of course).

    So you don't need to multiply anything, just shift the values. you need first something to store result, the result will be 24 bits at least, usually 32 bits are used with top-most 8 wasted as "padding".

    colorResult db 0, 0, 0, 0  ; B, G, R, (alfa/padding)
    

    Then let's say numR, numG, numB already contain their values.. as they are defined as WORD, the stored value may be outside of 0-255 range, which the following code will "sanitize" by simply truncating it (i.e. value 260 for red will end as R=4 (truncated to 8 bits)).

    mov   al,BYTE PTR [numB]
    mov   ah,BYTE PTR [numG]   ; this is doing *256
    ; because AH is 8 bits "left" to the al
    mov   WORD PTR [colorResult],ax  ; store first half of result
    mov   al,BYTE PTR [numR]
    xor   ah,ah                 ; set padding/alfa to 0
    mov   WORD PTR [colorResult+2],ax  ; this is doing *65536
    ; because that +2 is 16 bits shift, which is *65536
    

    Done.

    Just to make shifting more visible (as in previous example it is hidden by byte offsets and al/ah composition), one more example: in protected mode with 32b registers it is often needed to do the opposite, decompose the 24b RGB value into channels:

    ; eax = 24b RGB (B is low 8 bits)
    mov ebx,eax
    shr ebx,8     ; ebx will be G, this is /256
    mov ecx,eax
    shr ecx,16    ; ecx will be R, this is /65536
    mov edx,eax
    shr edx,24    ; edx will be alpha, this is /16777216 (2**24)
    ; eax will be B
    ; all channels (eax, ebx, ecx, edx) already contain correct value in low 8 bits
    ; so now all is needed to mask out any other bits left in upper parts
    movzx eax,al
    movzx ebx,bl
    movzx ecx,cl
    ; edx already contains only 8 bits of alpha, SHR did clear upper 24 bits
    

    So think about how values are encoded in computer, and how multiply by power of 2 can be done just by shifting bits to left (and unsigned divison by shifting bits right, signed almost works, except -1/2, which stays -1 when SAR shifted instead of proper division). And how handy it is, that RGB were defined as 8 bit values... It's not coincidence, it was done exactly to get such simple manipulation with separate values.

    On contrary there exist for example 16 bit RGB 5:6:5 format, which helps to save video/texture memory (only 2 bytes per pixel), but any colour manipulation has to do a bit more shifting+masking to get particular channel value, or to compose the values back into colour.


    EDIT: and of course there's no chance to fit 24b into 16b register, so you can't have final result in ax, that's why my first example stores the final 32b colour value into memory, not into register.

    To load that back into the dx:ax for example (two 16b registers = 32b) you can do:

    mov ax,[colorResult]     ; ax = B + G*256
    mov dx,[colorResult+2]   ; dx = R + alfa*256
    

    Or with 80386+ CPU you can use eax even in real mode, so:

    mov eax,[colorResult]    ; eax = B + G*256 + R*65536 + (alfa<<24)