Can anyone tell me the purely assembly code for displaying the value in a register in decimal format? Please don\'t suggest using the printf hack and then compile w
You need to turn a binary integer into a string/array of ASCII decimal digits manually. ASCII digits are represented by 1-byte integers in the range '0'
(0x30) to '9'
(0x39). http://www.asciitable.com/
For power-of-2 bases like hex, see How to convert a binary integer number to a hex string? Converting between binary and a power-of-2 base allows many more optimizations and simplifications because each group of bits maps separately to a hex / octal digit.
Most operating systems / environments don't have a system call that accepts integers and converts them to decimal for you. You have to do that yourself before sending the bytes to the OS, or copying them to video memory yourself, or drawing the corresponding font glyphs in video memory...
By far the most efficient way is to make a single system call that does the whole string at once, because a system call that writes 8 bytes is basically the same cost as writing 1 byte.
This means we need a buffer, but that doesn't add to our complexity much at all. 2^32-1 is only 4294967295, which is only 10 decimal digits. Our buffer doesn't need to be large, so we can just use the stack.
The usual algorithm produces digits LSD-first (Least Significant Digit first). Since printing order is MSD-first, we can just start at the end of the buffer and work backwards. For printing or copying elsewhere, just keep track of where it starts, and don't bother about getting it to the start of a fixed buffer. No need to mess with push/pop to reverse anything, just produce it backwards in the first place.
char *itoa_end(unsigned long val, char *p_end) {
const unsigned base = 10;
char *p = p_end;
do {
*--p = (val % base) + '0';
val /= base;
} while(val); // runs at least once to print '0' for val=0.
// write(1, p, p_end-p);
return p; // let the caller know where the leading digit is
}
gcc/clang do an excellent job, using a magic constant multiplier instead of div
to divide by 10 efficiently. (Godbolt compiler explorer for asm output).
Use this algorithm on the unsigned absolute value. (if(val<0) val=-val;
). If the original input was negative, stick a '-'
in front at the end, when you're done. So for example, -10
runs this with 10
, producing 2 ASCII bytes. Then you store a '-'
in front, as a third byte of the string.
Here's a simple commented NASM version of that, using div
(slow but shorter code) for 32-bit unsigned integers and a Linux write
system call. It should be easy to port this to 32-bit-mode code just by changing the registers to ecx
instead of rcx
. But add rsp,24
will become add esp, 20
because push ecx
is only 4 bytes, not 8. (You should also save/restore esi
for the usual 32-bit calling conventions, unless you're making this into a macro or internal-use-only function.)
The system-call part is specific to 64-bit Linux. Replace that with whatever is appropriate for your system, e.g. call the VDSO page for efficient system calls on 32-bit Linux, or use int 0x80
directly for inefficient system calls. See calling conventions for 32 and 64-bit system calls on Unix/Linux.
If you just need the string without printing it, rsi
points to the first digit after leaving the loop. You can copy it from the tmp buffer to the start of wherever you actually need it. Or if you generated it into the final destination directly (e.g. pass a pointer arg), you can pad with leading zeros until you reach the front of the space you left for it. There's no simple way to find out how many digits it's going to be before you start unless you always pad with zeros up to a fixed width.
ALIGN 16
; void print_uint32(uint32_t edi)
; x86-64 System V calling convention. Clobbers RSI, RCX, RDX, RAX.
global print_uint32
print_uint32:
mov eax, edi ; function arg
mov ecx, 0xa ; base 10
push rcx ; newline = 0xa = base
mov rsi, rsp
sub rsp, 16 ; not needed on 64-bit Linux, the red-zone is big enough. Change the LEA below if you remove this.
;;; rsi is pointing at '\n' on the stack, with 16B of "allocated" space below that.
.toascii_digit: ; do {
xor edx, edx
div ecx ; edx=remainder = low digit = 0..9. eax/=10
;; DIV IS SLOW. use a multiplicative inverse if performance is relevant.
add edx, '0'
dec rsi ; store digits in MSD-first printing order, working backwards from the end of the string
mov [rsi], dl
test eax,eax ; } while(x);
jnz .toascii_digit
;;; rsi points to the first digit
mov eax, 1 ; __NR_write from /usr/include/asm/unistd_64.h
mov edi, 1 ; fd = STDOUT_FILENO
lea edx, [rsp+16 + 1] ; yes, it's safe to truncate pointers before subtracting to find length.
sub edx, esi ; length=end-start, including the \n
syscall ; write(1, string, digits + 1)
add rsp, 24 ; (in 32-bit: add esp,20) undo the push and the buffer reservation
ret
Public domain. Feel free to copy/paste this into whatever you're working on. If it breaks, you get to keep both pieces.
And here's code to call it in a loop counting down to 0 (including 0). Putting it in the same file is convenient.
ALIGN 16
global _start
_start:
mov ebx, 100
.repeat:
lea edi, [rbx + 0] ; put +whatever constant you want here.
call print_uint32
dec ebx
jge .repeat
xor edi, edi
mov eax, 231
syscall ; sys_exit_group(0)
Assemble and link with
yasm -felf64 -Worphan-labels -gdwarf2 print-integer.asm &&
ld -o print-integer print-integer.o
./print_integer
100
99
...
1
0
Use strace
to see that the only system calls this program makes are write()
and exit()
. (See also the gdb / debugging tips at the bottom of the x86 tag wiki, and the other links there.)
I posted an AT&T-syntax version of this for 64-bit integers as an answer to
Printing an integer as a string with AT&T syntax, with Linux system calls instead of printf. See that for more comments about performance, and a benchmark of div
vs. compiler-generated code using mul
.
Related: NASM Assembly convert input to integer? is the other direction.