Page 1 of 1

64bit itoa in assembly.

Posted: Wed Nov 17, 2010 2:36 pm
by manlyman
When writing my kernel i need to be able to debug properly, which means i need to be able to print out numbers on the screen. So i set about writing an itoa() function. Finding an itoa written in assembly is very difficult however. There are some implementations out there after some googling but they all seem unneccessarily long and complicated (and slower). And dissasembling HLL output is a complete mess.

So i decided to write my own, after realising that the basic algorithm is just to divide by the requested base repeatedly to get each of the numbers in reverse order i made this:

Code: Select all

; Int to  Ascii
;
; Converts integer in rax into a string saved at buffer pointed to by rdi.
; Works by repeatedly dividing by base to get each number backwards and converting each to ascii.
;
; @param rax Integer to convert
; @param ebx base
; @param rdi pointer to buffer to save string to
;
; @return void
;
; @todo find out how big the buffer is going to need to be based on the base requested
io_itoa:
    ;find out how big the buffer needs to be for biggest number
    ;TODO
    ;divide by base repeatedly to get each number
    mov     ecx,20             ;maximum size the string could be (in base 10), this needs to be calculated
    @@:
    xor     edx,edx            ;clear rdx - clearing edx clears top half of rdx too, inconsistent with rest of architecture :/
    div     rbx                ;divide by the requested base in ebx
    add     edx,'0'            ;add ascii number for 0
    mov     byte[rdi+rcx-1],dl ;put numbers at end of buffer -> start of buffer
    cmp     eax,0              ;stop when the quotient is 0, no more numbers to convert
    je      @F
    dec     ecx
    jnz     @B
    @@:
    ;shift number left
    xor     ebx,ebx
    @@:
    mov     al,byte[rdi+rcx-1] ;load last byte that was written into al
    mov     byte[rdi+rcx-1],' ';clear where it came from
    mov     [rdi+rbx],al       ;store start of buffer->end of buffer
    inc     ecx                ;goto next byte written to by previous routine
    inc     ebx                ;goto next byte from start
    cmp     ecx,21             ;needs to be one higher, not entirely sure why though.
    jne     @B
    ;null terminate
    mov     byte[rdi+rbx],0
    ret         
It works however only with base 10. I can't figure out how big the buffer needs to be mathematically. I could write a lookup table for this but there's got to be a mathematical way of finding it out. My maths doesn't extend very far past high school level, so it's very possibly it's the most obvious thing in the world.

I'm also aware I've also got problems that the ascii table doesn't go from '9' straight to 'a' or 'A' so some more code is needed there for bases higher than 10. That's pretty trivial to fix though.

Basically i'd like to know if anyone has or know where i could find a well documented/commented itoa implementation preferably written in assembly that i could look at.

Re: 64bit itoa in assembly.

Posted: Wed Nov 17, 2010 3:09 pm
by Combuster
Solve:

Code: Select all

10^x >= 2^64
in other words, the biggest number you can describe in x digits should at least be the biggest number that 64 bits can describe.

Re: 64bit itoa in assembly.

Posted: Wed Nov 17, 2010 3:33 pm
by manlyman
Combuster wrote:Solve:

Code: Select all

10^x >= 2^64
in other words, the biggest number you can describe in x digits should at least be the biggest number that 64 bits can describe.
Ah. I think that's exactly what i needed. So i can just strlen the buffer handed to me and if:

Code: Select all

base^strlen < 2^64 
then i know the buffer is too small and can return error.

Re: 64bit itoa in assembly.

Posted: Mon Nov 22, 2010 8:45 am
by IanSeyler
Integer to String and String to Integer functions from BareMetal OS:

Code: Select all

; -----------------------------------------------------------------------------
; os_int_to_string -- Convert a binary interger into an string
;  IN:	RAX = binary integer
;	RDI = location to store string
; OUT:	RDI = points to end of string
;	All other registers preserved
; Min return value is 0 and max return value is 18446744073709551615 so your
; string needs to be able to store at least 21 characters (20 for the digits
; and 1 for the string terminator).
; Adapted from http://www.cs.usfca.edu/~cruse/cs210s09/rax2uint.s
os_int_to_string:
	push rdx
	push rcx
	push rbx
	push rax

	mov rbx, 10					; base of the decimal system
	xor ecx, ecx					; number of digits generated
os_int_to_string_next_divide:
	xor edx, edx					; RAX extended to (RDX,RAX)
	div rbx						; divide by the number-base
	push rdx					; save remainder on the stack
	inc rcx						; and count this remainder
	cmp rax, 0					; was the quotient zero?
	jne os_int_to_string_next_divide		; no, do another division

os_int_to_string_next_digit:
	pop rax						; else pop recent remainder
	add al, '0'					; and convert to a numeral
	stosb						; store to memory-buffer
	loop os_int_to_string_next_digit		; again for other remainders
	xor al, al
	stosb						; Store the null terminator at the end of the string

	pop rax
	pop rbx
	pop rcx
	pop rdx
	ret
; -----------------------------------------------------------------------------


; -----------------------------------------------------------------------------
; os_string_to_int -- Convert a string into a binary interger
;  IN:	RSI = location of string
; OUT:	RAX = integer value
;	All other registers preserved
; Adapted from http://www.cs.usfca.edu/~cruse/cs210s09/uint2rax.s
os_string_to_int:
	push rsi
	push rdx
	push rcx
	push rbx

	xor eax, eax			; initialize accumulator
	mov rbx, 10			; decimal-system's radix
os_string_to_int_next_digit:
	mov cl, [rsi]			; fetch next character
	cmp cl, '0'			; char preceeds '0'?
	jb os_string_to_int_invalid	; yes, not a numeral
	cmp cl, '9'			; char follows '9'?
	ja os_string_to_int_invalid	; yes, not a numeral
	mul rbx				; ten times prior sum
	and rcx, 0x0F			; convert char to int
	add rax, rcx			; add to prior total
	inc rsi				; advance source index
	jmp os_string_to_int_next_digit	; and check another char
	
os_string_to_int_invalid:
	pop rbx
	pop rcx
	pop rdx
	pop rsi
	ret
; -----------------------------------------------------------------------------