UMBC CMSC 313 -- Numeric Input/Output Previous | Next


Numeric Input/Output

Output Discussion

Up to this point, the emphasis on I/O involved using scanf/printf. They accomplish a lot! However, they do add a lot to the size of things, and usually more capability than is needed. Let's look at how to find out how to get the ASCII equivalent of 109. (The fact that it is stored in binary inside the computer makes no difference.)

Let's divide by 10 and get the remainder! 109/10 = 10 remainder 9. Since we will only get remainders of 0 through 9, it is real easy to convert to ASCII. All we have to do is add 30h to the remainder and that gives us the prinable ASCII character.
Remainder Conversion ASCII result
0 30h + 0 '0'
1 30h + 1 '1'
2 30h + 2 '2'
3 30h + 3 '3'
4 30h + 4 '4'
5 30h + 5 '5'
6 30h + 6 '6'
7 30h + 7 '7'
8 30h + 8 '8'
9 30h + 9 '9'

If we have that in an array (left-side) and continuing dividing the quotient until it comes zero, we have the entire process!

109/10 = 10 remainder 9
        '9'

10/10 = 1 remainder 0
      '0' '9'

1/10 = 0 remainder 1 (This is our last step, since the quotient is now zero.)
    '1' '0' '9'

The Design

We don't know in advance how big the values might be, except that they are signed 32-bit integers (int in C). The smallest negative number is -2147483648 and the largest positive number is 2147483647. The -2147483648 is a special case, because there is no +2147483648. All other negative numbers have a corresponding positive number. For the rest, we can keep track the sign. If the value is negative, change the sign. Then convert. When done, if it is negative, add the '-' to the output buffer and we are finished! Since the divident is 32-bits, the divisor must be 32-bit. We will put 10 into ebx, and things are good. Whoops, we need to convert the divident to 64-bits in order for this to work correctly! Convert the double word to a quad word and do a signed divide.

To store this into the output buffer, we can use the ESI indexing register and point to the last address of the buffer. As we fill the array, decrement the address and increment the count of output characters.

The Code

doLoop:
        cdq
        idiv    ebx
        add     dl, 30h
        mov     byte [ esi ], dl
        dec     esi
        inc     ecx
        cmp     eax, 0
        jne     doLoop

We don't care about the leading unfilled positions of the output buffer. We can simply print out starting at the address of the beginning ASCII characters, for the number of characters!

In order to make things more general, make subprograms reuseable and try to no variables, just registers or the stack! Finally, write code that minimizes the destructive use of registers.

;========================================================================================
;;
;; numOut -- receives a signed integer in EAX and outputs it to the screen.
;;           To make things more reusable, the conversion is done is
;;           a subprogram, nrCnvt.
;;
;; registers used:  None.  All are restored.
;========================================================================================

numOut: 
        push    esi
        push    eax
        push    ebx
        push    ecx
        push    edx

        mov     esi, dword nrOut      ; Go to the start of output buffer
        add     esi, nrLen            ; Jump to the end of it (one past, actually)
        dec     esi                   ; Back up to the last byte of buffer
        call    nrCnvt
        
        mov      edx, eax             ; Number of bytes to write
        mov      eax, WRITE           ; System Call -- read
        mov      ebx, STDOUT          ; File descriptor opened for reading
        mov      ecx, esi             ; Pointer to input buffer because it is 
        int      80h

        pop      edx
        pop      ecx
        pop      ebx
        pop      eax
        pop      esi
        ret


;========================================================================================
;; nrCnvt -- receives a signed 32-bit integer in EAX and converts it to ASCII
;;           for output.  ESI has the address of an output buffer to put
;;           ASCII characters while converting.  NOTE:  This does not correctly
;;           convert the single value that is MININT (INT_MIN) or -2147483648,
;;           To be technically correct, this special case should be checked and 
;;           handled.  "This exercise is left to the reader."
;;
;;           To make this truely portable and reusable, you could make this 
;;           so that CL holds the count and CH holds the sign.
;;
;;  registers used:
;;           EAX   -- contents destoryed, returns number of characters to print.
;;      
;========================================================================================

nrCnvt:
        push    ebx
        push    ecx
        push    edx

        mov     ebx, 10
        mov     ecx, 0
        mov     byte [ signFlag ], ' '   ; Try to eliminate this variable!
        cmp     eax, 0
        jge     doLoop
        mov     byte [ signFlag ], '-'
        neg     eax
     
doLoop:
        cdq
        idiv    ebx
        add     dl, 30h
        mov     byte [ esi ], dl
        dec     esi
        inc     ecx
        cmp     eax, 0
        jne     doLoop
        
        mov     dl, byte [ signFlag ]
        mov     byte [ esi ], dl
        inc     ecx
        mov     eax, ecx      ; return number of character to output
 
        pop     edx
        pop     ecx
        pop     ebx
        ret

Input Discussion

The input goes the other direction, you get the ASCII characters in the input buffer, then you starting converting from the most significant digit.
    '1' '0' '9'

If we consider this from the ASCII hex values, we get:

    31h 30h 39h

Get the first character ('1'), subtract 30h, gives:

1

Now multiple that result by 10, get the next character ('0') and subtract 30h and add to the previous result:

10

Now multiple that result by 10, get the next character ('9') and subtract 30h and add to the previous result:

109

Of course, you have to properly initialize the variable receiving the interim results. In this case, there can only be the digits '0' through '9', but once again, all you know is that it will be a signed 32-bit integer value when you are done. Of course, the first thing you will have to check is to see if you have a sign character and keep track of it. You also will need to be more worried with the quality of the input. Users make mistakes and you will have to do the appropriate error checking. None of our discussion concerns the input and output of floating point numbers. That is too complex for our discussions here, and most assembly language programming does not require it. Use scanf and printf for that!


Previous | Next

©2005, Gary L. Burt