Got my d2a working(stupid pointer messup). I've choped it down as far as I can manage. There is two versions, one requires some of the math be donre in the invoke.
V1:


_TEXT SEGMENT
MakeNumber PROC PUBLIC value:PTR DWORD, return:PTR DWORD

mov eax,[esp+4] ;get dword
mov ebp,eax

test eax,-1 ;test negative
jns @F ;no?

cdq ;remove sign
add eax, edx
xor eax, edx
@@: ;go here
mov edi,[esp+8] ;get destination
mov esi,11 ;esi=last byte
mov [edi][esi],BYTE PTR 0 ;0 terminate in case buffer is corrupted
mov ecx,10
@@:
xor edx, edx
div ecx

dec esi
lea ebx,[edx+48] ;change to ASCII

mov [edi][esi],BYTE PTR bl ;put byte in buffer

test eax,eax
jnz @B ;exit when eax = 0

test ebp,-1 ;was original number negative?
jns @F ;no?

mov ebx,'-'
dec esi
mov [edi][esi],BYTE PTR bl
@@: ;go here
lea eax,[edi][esi] ;standard return in eax of addr of 1st byte

ret
MakeNumber endp
_TEXT ENDS

V@ needs an invoke like this:


invoke MakeNumber,-12345678,ADDR buffer[B]+11[/B]

Since I building the string from least to most significant digit, adding +11 gives my the last address in a 12 byte buffer(12 bytes is all that is needed to display any interger number from a dword)
V2:


_TEXT SEGMENT
MakeNumber PROC PUBLIC value:PTR DWORD, return:PTR DWORD

mov eax,[esp+4]
mov ebp,eax
[COLOR=red]mov esi,11[/COLOR=red] save 1 instruction
test eax,-1
jns @F

cdq
add eax, edx
xor eax, edx
@@:
mov edi,[esp+8]
mov [edi],BYTE PTR 0
mov ecx,10
@@:
xor edx, edx
div ecx

dec edi
lea ebx,[edx+48]

mov [edi],BYTE PTR bl

test eax,eax
jnz @B

test ebp,-1
jns @F

mov ebx,'-'
dec edi
mov [edi],BYTE PTR bl
@@:
lea eax,[edi]

ret 8
MakeNumber endp
_TEXT ENDS

I could also save another two instruction, by requiering the parameter be in registers, but none of these saving are inner loop stuff. Now if I could only do something about that div......
Posted on 2002-10-24 06:01:05 by ThoughtCriminal
Made and odd curiosity. This version builds the string on the stack above the stack pointer value before the invoke. One less paramter, and the data is temporary.


_TEXT SEGMENT
MakeNumber PROC PUBLIC value:PTR DWORD
mov eax,[esp+4]
mov esi,eax

test eax,-1
jns @F

cdq
add eax, edx
xor eax, edx
@@:
lea edi,[esp+23] ;this is the correct value, esp has been modified with invoke
mov [edi],BYTE PTR 0
mov ecx,10
@@:
xor edx, edx
div ecx

dec edi
lea ebx,[edx+48]

mov [edi],BYTE PTR bl

test eax,eax
jnz @B

test esi,-1
jns @F

mov ebx,'-'
dec edi
mov [edi],BYTE PTR bl
@@:
lea eax,[edi]

ret 4
MakeNumber endp
_TEXT ENDS
Posted on 2002-10-24 15:01:27 by ThoughtCriminal
Tests for signed values
uses AAM to eliminate every other div
stores it first to the textbuffer on the stack
then reverses the buffer to the destination string
and zero terminates it.

return value is the pointer to the *end* of the string (I find
this particularly useful especially if you have to append strings
right after or when converting several numbers ex. 04-04-2002)

Needs some optimization. I think the copy at the end could
be improved or avoided...




IntToStr PROC uses ebx ecx edx esi edi value,lpDest:DWORD
LOCAL textbuffer[12]:BYTE ;Handy buffer
mov esi,lpDest ;esi -> destination string
mov eax,value ;eax is value
test eax,eax ;is the value negative?
jns @F ;no? jump
mov BYTE PTR [esi],'-' ;yes? prefix our string
neg eax ;eax = abs (value)
inc esi ;move up a byte
@@: lea ebx,textbuffer ;get our handy buffer
xor edi,edi ;clear out a reg
mov ecx,100 ;prepare for divide (by 100)
@@: xor edx,edx ;clear edx for the div
div ecx ;do the div
xchg edx,eax ;swap eax and edx
aam ;let aam replace a div by 10
add ax,3030h ;convert to ascii
mov WORD PTR [ebx+edi],ax ;save it in textbuffer
add edi,2 ;walk along the buffer
xchg eax,edx ;restore eax
test eax,eax ;are we done?
jnz @B ;no? go back and do some more
xchg eax,esi ;yes? let eax->destination string
@@: dec edi ;do we have a one character string?
jz @F ;yes? jump forward
cmp BYTE PTR [edi+ebx],30h ;no? look for first byte that isn't '0'
je @B ;haven't found it? try again...
@@: mov dl,BYTE PTR [edi+ebx] ;found it. Load it up
mov BYTE PTR [eax],dl ;save it to the string
inc eax ;adjust destination pointer
sub edi,1 ;adjust source pointer
jns @B ;are we done? no? go back
mov BYTE PTR [eax],0 ;yes. Store the null-terminator
ret
IntToStr endp




--Chorus
Posted on 2002-10-24 20:44:53 by chorus

return value is the pointer to the *end* of the string (I find
this particularly useful especially if you have to append strings
right after or when converting several numbers ex. 04-04-2002)

Hmmm good point.

My second version where you invoke:


invoke MakeNumber,-12345678,ADDR buffer+11

Takes care of that because you give it the end address, and returns the start address. So you have both.


LOCAL textbuffer[12]:BYTE ;Handy buffer

I just learned something. I might be doing similar in my stack version, but in my version the data persists after function exit. They way i figure it, if you are converting to ASCII, the next operation will probly be to pass a pointer to another function(print,file,etc)""In many cases"".
So you dont really need a declared buffer to hold the data. In other words, if it is an immediate use case, I can do this:


invoke MakeNumber,80000000h
;eax=startaddress , esp+12=endaddress
invoke MessageBox,hwnd,eax,NULL,MB_OK

But for more persistant stuff version number 2 is better.

I don't really understand why you build the string left-to-right. If you build it right-to-left(backwards), you can take advantage of the fact that div will return the remainder for each iteration, from least to most significant.
Posted on 2002-10-25 00:33:14 by ThoughtCriminal
A slightly different version:

-builds strings right-to-left to simplify the copy at the end
-eliminates the need to use ebx
-because it's built backwards as opposed to before, you need to ror ax,8 to get ah and al in the right order before you save it to the buffer

Note that you can easily modify it work like your second version i.e., passing a pointer to the end of the string where the number should be. This of course means your number is right justified, which may often be the case. In my previous version, the reason I build the string backwards is because I always pass where the string should start. In most cases it's to build a sentence out of a string ex., "I have 101596 kilometers on my car". Since the string of the number can be variable length it makes sense for me to do it this way.

--Chorus




IntToStr2 PROC uses ecx edx esi edi value,lpDest:DWORD
LOCAL TextBuffer[12]:DWORD
mov eax,value
mov edi,lpDest
test eax,eax
jns @F
mov BYTE PTR [edi],'-'
inc edi
neg eax
@@: lea esi,[TextBuffer][12]
mov ecx,100
@@: xor edx,edx
div ecx
xchg edx,eax
aam
sub esi,2
add ax,3030h
ror ax,8
mov [esi],ax
xchg eax,edx
test eax,eax
jnz @B
lea ecx,[TextBuffer][12]
cmp BYTE PTR [esi],'0'
jne @F
inc esi
@@: sub ecx,esi
rep movsb
mov BYTE PTR [edi],0
mov eax,edi
ret
IntToStr2 ENDP
Posted on 2002-10-25 11:55:05 by chorus
Couple of little optimizations:

replace


test eax,eax
jns @F
mov BYTE PTR [edi],'-'
inc edi
neg eax

with


cdq
mov BYTE PTR [edi],'-'
sub edi,edx
add eax,edx
xor eax,edx

and replace


xchg eax,edx
test eax,eax
jnz @B

with


xor edx,eax
xor eax,edx
jnz @B

and also


lea ecx,[TextBuffer][12]
cmp BYTE PTR [esi],'0'
jne @F
inc esi

with


lea ecx,[TextBuffer][12]
cmp BYTE PTR [esi],'1'
adc esi,0


--Chorus
Posted on 2002-10-25 12:08:39 by chorus
Interesting. Regarding mine:

Version1 is pass the start of the buffer to the function.

Version2 is pass the end of the buffer to the function.

Version3 is buffer is built in positive stack space.




I realized I have a horrible bug in Version 1. If the function is call in a proc that uses a stack frame and locals, using ebp will trash the locals. So I either need to save ebp, or find a way to remove usage.
Posted on 2002-10-25 13:09:42 by ThoughtCriminal
If I'm not mistaken, you only use ebp to preserve and then only do a test at the end for sign. So why not just do the test directly on ?

BTW also in version 1 you can remove the test eax,-1/jns @F. The cdq/add eax,edx/xor eax,edx is meant to calculate absolute value without jumping. So having the jump anyways defeats the purpose. You might as well use neg eax it's shorter and will do the same thing. But the abs without jump is usually faster.

Also you should be able to free ebx. Instead of lea ebx,/mov ,bl just do add edx,48/mov ,dl since you're only gonna trash edx when you loop back. Then at the end use mov BYTE PTR ,'-' instead of mov ,bl

--Chorus
Posted on 2002-10-25 13:25:00 by chorus
ebp fix:


_TEXT SEGMENT
MakeNumber PROC PUBLIC value:PTR DWORD, return:PTR DWORD
mov eax,[esp+4]
mov esi,eax

test eax,-1
jns @F
cdq
add eax, edx
xor eax, edx
@@:
mov edi,[esp+8]
[B]lea edi,[edi+11][/B] ;adjust the start ot the end in the proc, use esi for original value
mov [edi],BYTE PTR 0
mov ecx,10
@@:
xor edx, edx
div ecx
dec edi
lea ebx,[edx+48]
mov [edi],BYTE PTR bl
test eax,eax
jnz @B

test esi,-1
jns @F

mov ebx,'-'
dec edi
mov [edi],BYTE PTR bl
@@:
lea eax,[edi]
ret
MakeNumber endp
_TEXT ENDS

Between V1 and V2, and V2.2, V2 is still the most efficient because the adjustment of the pointer to the end is done at assemble time, not run time.
Posted on 2002-10-25 13:35:56 by ThoughtCriminal
This post was in my "to check" queue.. long time though. :P

Hi Chorus, you wrote:
stores it first to the textbuffer on the stack
then reverses the buffer to the destination string

The following naive code may be faster (haven't tested it though), it's quite branch-prediction friendy anyway:


;find number of digits in advance (i.e. find integer log10)
MOV EBX,10
CMP EAX,1000000000
JAE .found
DEC EBX
CMP EAX,100000000
JAE .found
DEC EBX
CMP EAX,10000000
JAE .found
DEC EBX
CMP EAX,1000000
JAE .found
DEC EBX
CMP EAX,100000
JAE .found
DEC EBX
CMP EAX,10000
JAE .found
DEC EBX
CMP EAX,1000
JAE .found
DEC EBX
CMP EAX,100
JAE .found
DEC EBX
CMP EAX,10
JAE .found
DEC EBX
.found:

Then (in EBX) you will know the number of digits, and thus you can advance the string pointer accordingly, and start from there to print the digits in reverse.
Alternatively you may try to store the digits in regs, then BSWAP and SHLD.. but it'd probably be slower.

I agree++ with you that returning the updated string pointer (e.g. reg EDI) is the wisest choice, because that lets you easily concatenate strings.
I think that you shouldn't terminate the string, though, because of the "concatenation bonus" issue of above.

When making/formatting strings, I like to use EDI as dest pt and ESI as source pt, and then perform several string operations cascaded. It's quite fast this way.
Posted on 2002-11-17 06:19:13 by Maverick
Hello Maverick,
Here's what the last version I made looks like. It only uses one conditional jump, but still builds the string on the stack.

I took a quick look at re-writing the code with your suggestion of pre-calculating the length, but I don't think it's suitable for my algorithm. The problem is with the AAM instruction, which I use to reduce the number of divs performed (I think AAM is about 25 clocks faster than DIV). I write 2 bytes at a time (to the stack) using this method, and so to write to the destination directly, I would need to watch for an odd digit length inside the loop. This additional logic in the inner loop plus a loop to pre-calculate the number length might be costly.

As for terminating the string my reasoning is this: if I want to concatenate the string afterwards, the return value points to the zero byte, so the string copy that follows will overwrite it. And if you don't concatenate, then it's already terminated for you (read: I keep forgetting to terminate my strings after I call IntToStr so this was easier :)) You can, of course, eliminate that line.

Thanks
--Chorus





IntToStr PROC uses ecx edx esi edi value,lpDest:DWORD
LOCAL TextBuffer[12]:DWORD

mov eax,value ;Initialization
mov edi,lpDest
cdq
mov BYTE PTR [edi],'-'
sub edi,edx
mov ecx,100
add eax,edx
lea esi,[TextBuffer][12]
xor eax,edx

@@: xor edx,edx ;Inner Loop
div ecx
xchg edx,eax
aam
sub esi,2
add ax,3030h
ror ax,8
mov [esi],ax
xor edx,eax
xor eax,edx
jnz @B

lea ecx,[TextBuffer][12] ;String Copy
cmp BYTE PTR [esi],'1'
adc esi,0
sub ecx,esi
rep movsb
mov BYTE PTR [edi],0
mov eax,edi
ret
IntToStr ENDP
Posted on 2002-11-17 09:50:03 by chorus
Hi Chorus,
Yup, and we risk to give too many attentions to the optimization of a routine that, after all, isn't the most "time critical" in typical programs. ;)

About the termination thing you've made the wisest and most comfortable choice, it's the "professional deformation" of optimizing code that makes me prefer to explicitly add it at the end. ;)
But I guess that one should never exaggerate. :tongue:
Posted on 2002-11-17 15:42:35 by Maverick