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!