| UMBC | CMSC 313 -- Numeric Input/Output | Previous | Next |
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' |
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.
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
| '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!