MASM for FUN - #0.0 [Nested Loops - formatted numbers - msgbox]

Started by frktons, May 05, 2010, 05:56:39 PM

Previous topic - Next topic

frktons

Here we are again.

A MASM32 only exercise [food for thought]  about loops, formatting numbers
and a final call to an API, the mythical msgbox, to display the results.

We start with a routine in MASM32 that Dave provided elsewhere:
http://www.masm32.com/board/index.php?topic=13912.0

the routine executes 5 iteration of a nested loop that generates all
the combinations of 4 integer numbers ranging 1 - 100 and calculate
the total amount of combinations and the elapsed CPU cycles.

This is the original routine:


        include \masm32\include\masm32rt.inc
        .586
        include \masm32\macros\timers.asm

LoopCount = 250

;----------------------------------------------------------

        .code

_main   proc

;restrict execution to a single core

        INVOKE  GetCurrentProcess
        INVOKE  SetProcessAffinityMask,eax,1

;count and display combinations

        call    combo
        print   ustr$(eax),'  combinations',13,10

;run timing test 5 times

        mov     ecx,5

time00: push    ecx
        counter_begin LoopCount,REALTIME_PRIORITY_CLASS
        call    combo
        counter_end
        print   ustr$(eax),' clock cycles',13,10
        pop     ecx
        dec     ecx
        jnz     time00

;terminate

        inkey
        exit

_main   endp

;----------------------------------------------------------

combo   proc

        push    ebx
        push    ecx
        push    edx
        push    esi
        xor     ebx,ebx
        mov     eax,ebx

combo1: inc     ebx
        mov     ecx,ebx

combo2: inc     ecx
        mov     edx,ecx

combo3: inc     edx
        mov     esi,edx

combo4: inc     esi
        inc     eax
        cmp     esi,100
        jb      combo4

        cmp     edx,99
        jb      combo3

        cmp     ecx,98
        jb      combo2

        cmp     ebx,97
        jb      combo1

        pop     esi
        pop     edx
        pop     ecx
        pop     ebx
        ret

combo   endp

;----------------------------------------------------------

        end     _main



and this is the unformatted output:


3921225  combinations
15996963 clock cycles
15819197 clock cycles
15802771 clock cycles
15769103 clock cycles
15820043 clock cycles


What are we going to do?

First we create or CALL an existing function [I actually don't know
if one is already there] to format the numbers with comma delimiters:

3921225  combinations   ===> 3,921,225  combinations


That's just because I like numbers in that fashion  :P

And at the end of the routine we call a Win32 API to display
the results inside a Message Box.

With Dave's permission we are going to add these small features
to the MASM32 routine.

I have to confess that I am only partially aware of how to do that,
so any help or suggestion is welcome  :wink

Frank



Mind is like a parachute. You know what to do in order to use it :-)

joemc

i'm working on figuring out the way to make the number comma separated without using a new set of memory,
i am a little stuck though because you have to work backwards up the string to avoid overwriting a value you stil
l need, but i only know how to calculate where to move them going forward.

can easily make this a macro instead of function when i finish figuring it out.
comma separated unsigned-int-to string
csustr$()



include \masm32\include\masm32rt.inc

.data?
value dd ?

.data
numb DWORD 4294967295
.code

start:

cls

; i decided because ustr$ designates 20 bytes in data section
; and is only using 11 of it, and i am at most adding three commas,
; it would be ok to use the memory it provides, if your macro
; for ustr changes, you may have a problem
mov eax,ustr$(numb)

; i am sure there is a better way to
; get strlen. may even be able to calculate
; based off the dword
xor ecx,ecx
sub ecx,1
count:
add ecx,1
movzx edx,byte ptr [eax+ecx]
test edx,edx
jnz count

;0123456789ABC
;4,294,967,295
;4294967295
;9->c
;8->b
;7->a
;6->8
;5->7
;4->6
;3->4
;2->3
;1->2
;0->0

;01234
;1,000
;1000
;3->4
;2->3
;1->2
;0->0

;012345
;10,000
;10000
;4->5
;3->4
;2->3
;1->1
;0->0


movzx edx,byte ptr [eax+09h]
mov [eax+0Ch], dl
movzx edx,byte ptr[eax+08h]
mov [eax+0Bh], dl
movzx edx,byte ptr[eax+07h]
mov [eax+0Ah], dl
movzx edx,byte ptr[eax+06h]
mov [eax+08h], dl
movzx edx,byte ptr[eax+05h]
mov [eax+07h], dl
movzx edx,byte ptr[eax+04h]
mov [eax+06], dl
movzx edx,byte ptr[eax+03h]
mov [eax+04h], dl
movzx edx,byte ptr[eax+02h]
mov [eax+03h], dl
movzx edx,byte ptr[eax+01h]
mov [eax+02h], dl

mov byte ptr [eax+01h],2Ch
mov byte ptr [eax+05h],2Ch
mov byte ptr [eax+09h],2Ch

pushad
print eax,13,10
popad
print str$(ecx)

inkey
exit
end start


dedndave

the C-runtime library may have a function that already does this
printf comes to mind - it does a lot of different numeric formatting

joemc

Quote from: dedndave on May 06, 2010, 03:16:02 AM
the C-runtime library may have a function that already does this
printf comes to mind - it does a lot of different numeric formatting
well i could just use another 14 bytes and do it without c runtime library :) i was just trying to figure out how to use the same 20 without too long of code.

dedndave

without looking at the print macro, i don't know if it is left justified or right
at any rate, memory is not so precious that you wouldn't want to use your own buffer
i bet JJ has a routine for this already written for MASM Basic, too   :bg
it isn't a hard routine to write, though
it's just that windows uses some function or other all the time to display file sizes


dedndave

damn !!! - lol
well - i would start by finding the end of the string
then work toward the beginning
your function could use a right-justified buffer to make the code simple

frktons

Quote from: joemc on May 06, 2010, 03:37:24 AM
Quote from: dedndave on May 06, 2010, 03:16:02 AM
the C-runtime library may have a function that already does this
printf comes to mind - it does a lot of different numeric formatting
well i could just use another 14 bytes and do it without c runtime library :) i was just trying to figure out how to use the same 20 without too long of code.

I was thinking, last night before falling asleep, to use the 20 bytes string
returned from ustr$ to do the job:

As Dave says:

Quote from: dedndave on May 06, 2010, 04:09:42 AM
damn !!! - lol
well - i would start by finding the end of the string
then work toward the beginning
your function could use a right-justified buffer to make the code simple

We could start from the end of the string, using 3 bytes at a time.
But I think it's better to use an other variable string to build the formatted one.

source string: "23456789"   ===> target string: "xx,xxx,xxx" 

1) we get the 3 rightmost character of souce string and put them into the
    3 rightmost characters of target string:

    "789"   ===>  "xx,xxx,789"  using the appropriate address
2) the same with the second group of 3 characters:

    "456"  ====>  "xx,456,789"   using the appropriate address
3) the last 2 characters go to the appropriate position.

Of course we have to prebuild the target string depending on the lenght
of the source string, and we have to check if moving 3, 2, or 1 byte
when we move the leftmost group of characters.

We could use an array of prebuild target strings and use the lenght of
the variable to format as an index to the array, so we get the proper
target format without many checking, using some 50-80 bytes for the array,
but no CMP or any check at all.

It shouldn't be too difficult.

If it is a function, it returns the formatted integer number as a
comma separated string.
If it is MACRO, you know what it does, I actually don't.
If it is a Proc, it could use the variables defined in the .data section.

That is one possible solution in ASM.
The solutions could be many, let's try some of them.
Mind is like a parachute. You know what to do in order to use it :-)

frktons

Mind is like a parachute. You know what to do in order to use it :-)

jj2007

Quote from: dedndave on May 06, 2010, 03:46:49 AM
i bet JJ has a routine for this already written for MASM Basic, too   :bg

Well, there is no in-built function, but it is straightforward:

Quoteinclude \masm32\MasmBasic\MasmBasic.inc   ; http://www.masm32.com/board/index.php?topic=12460
.data
MyFatNumber1   dq 123456789012345678   ; 123,456,789,012,345,678
MyFatNumber2   dq 12345678901234567
MyFatNumber3   dq 1234567890123456
MyFatNumber4   dq 123456789012345

.data?
buffer   db 1024 dup(?)
   
Init
[/color]
   push esi
   
push edi
   push ebx
   ct = 0
   REPEAT 4
      ct = ct + 1
      @CatStr(<mov esi, Str$(q:MyFatNumber>, %ct, <)>)
      mov edi, offset buffer
      push edi
      push Len(esi)
      cdq
      mov ecx, 3
      div ecx
      dec edx   ; edx is   0, 2, 1, 0
      .if Sign?   ; we need   2, 1, 0, 2
            add edx, 3
      .endif
      pop ecx   ; len of source
      mov al, ","
      .Repeat
            movsb
            dec ecx
            .Break .if Zero?
            dec edx
            .if Sign?
               stosb
               add edx, 3
            .endif
      .Until 0
      xor eax, eax
      stosd   ; set a zero delimiter
      pop edi
      Print edi, CrLf$
   ENDM
   pop ebx
   pop edi
   pop esi
   Inkey Str$("\n\nYour puter has run %3f hours since the last boot, give it a break!", Timer()/3600000)
   
Exit
end start

123,456,789,012,345,678
12,345,678,901,234,567
1,234,567,890,123,456
123,456,789,012,345

frktons

Quote from: jj2007 on May 06, 2010, 11:08:46 AM
Quote from: dedndave on May 06, 2010, 03:46:49 AM
i bet JJ has a routine for this already written for MASM Basic, too   :bg

Well, there is no in-built function, but it is straightforward:

Quoteinclude \masm32\MasmBasic\MasmBasic.inc   ; http://www.masm32.com/board/index.php?topic=12460
.data
MyFatNumber1   dq 123456789012345678   ; 123,456,789,012,345,678
MyFatNumber2   dq 12345678901234567
MyFatNumber3   dq 1234567890123456
MyFatNumber4   dq 123456789012345

.data?
buffer   db 1024 dup(?)
   
Init
[/color]
   push esi
   
push edi
   push ebx
   ct = 0
   REPEAT 4
      ct = ct + 1
      @CatStr(<mov esi, Str$(q:MyFatNumber>, %ct, <)>)
      mov edi, offset buffer
      push edi
      push Len(esi)
      cdq
      mov ecx, 3
      div ecx
      dec edx   ; edx is   0, 2, 1, 0
      .if Sign?   ; we need   2, 1, 0, 2
            add edx, 3
      .endif
      pop ecx   ; len of source
      mov al, ","
      .Repeat
            movsb
            dec ecx
            .Break .if Zero?
            dec edx
            .if Sign?
               stosb
               add edx, 3
            .endif
      .Until 0
      xor eax, eax
      stosd   ; set a zero delimiter
      pop edi
      Print edi, CrLf$
   ENDM
   pop ebx
   pop edi
   pop esi
   Inkey Str$("\n\nYour puter has run %3f hours since the last boot, give it a break!", Timer()/3600000)
   
Exit
end start

123,456,789,012,345,678
12,345,678,901,234,567
1,234,567,890,123,456
123,456,789,012,345


Thanks jj  :U

So it's time to have a look at your masmbasic library  :P
For an old friend of BASIC dialects like me, it should be fine.

may I ask you if there are the asm sources of your lib
functions in order to study them?

Frank
Mind is like a parachute. You know what to do in order to use it :-)

Ghandi

First off i want to make clear that this is by no means optimized code for anything, it was thrown together just to see if it would work:


;
;Test of string formatting routine.
;
;Input string = 1234567890123456789012345
;        (25 decimal characters)
;
;Output string = 1,234,567,890,123,456,789,012,345
;        (33 decimal characters)
;
;Press enter to exit...

FormatString PROTO pInput:DWORD,pOutput:DWORD

FormatString PROC pInput:DWORD,pOutput:DWORD

;Get length of string
mov edi,pInput
xor eax,eax
or ecx,-1
repne scasb
add ecx,1
not ecx

;Get number of iterations plus remainder
mov eax,ecx
mov ecx,3
xor edx,edx
div ecx

;Set source and destination registers, string length and then move across remainder as left most of string
mov esi,pInput
mov edi,pOutput
mov ecx,edx
rep movsb

;Set counter to number of iterations
mov ecx,eax

;Set EDX to AND mask
mov edx,0FFFFFFh

;We'll start each part here with the ',' and then take a full DWORD from EDI as we know there is the NULL byte at the end.
mov ebx,','

@@ThreeCharLoop:
;Save delimiter
mov byte ptr [edi],bl
add edi,1

;Load source DWORD
lodsd

;Strip upper byte of EAX with AND mask in EDX
and eax,edx

;Save DWORD to destination as null terminated 3 char string
stosd

;There is a byte overlap from the LODSD/STOSD, decrement source and destination by one byte
sub esi,1
sub edi,1

;Loop like this until ECX == 0
sub ecx,1
jnz @@ThreeCharLoop

;Get length of output string
mov edi,pOutput
xor eax,eax
or ecx,-1
repne scasb
add ecx,1
not ecx

;Set return value
mov eax,ecx
Ret

FormatString EndP


HR,
Ghandi

Ghandi

Another thought also, you could eliminate the destination parameter and use stack memory to accommodate the input string, copy the input and then build the output string in the input buffer using the stack copy for reference. Vice versa, you could use the stack memory to hold the temp string and then once formatting has taken place you could copy the resulting string across to the input buffer.

HR,
Ghandi

frktons

Quote from: Ghandi on May 06, 2010, 12:17:31 PM
First off i want to make clear that this is by no means optimized code for anything, it was thrown together just to see if it would work:


;
;Test of string formatting routine.
;
;Input string = 1234567890123456789012345
;        (25 decimal characters)
;
;Output string = 1,234,567,890,123,456,789,012,345
;        (33 decimal characters)
;
;Press enter to exit...

FormatString PROTO pInput:DWORD,pOutput:DWORD

FormatString PROC pInput:DWORD,pOutput:DWORD

;Get length of string
mov edi,pInput
xor eax,eax
or ecx,-1
repne scasb
add ecx,1
not ecx

;Get number of iterations plus remainder
mov eax,ecx
mov ecx,3
xor edx,edx
div ecx

;Set source and destination registers, string length and then move across remainder as left most of string
mov esi,pInput
mov edi,pOutput
mov ecx,edx
rep movsb

;Set counter to number of iterations
mov ecx,eax

;Set EDX to AND mask
mov edx,0FFFFFFh

;We'll start each part here with the ',' and then take a full DWORD from EDI as we know there is the NULL byte at the end.
mov ebx,','

@@ThreeCharLoop:
;Save delimiter
mov byte ptr [edi],bl
add edi,1

;Load source DWORD
lodsd

;Strip upper byte of EAX with AND mask in EDX
and eax,edx

;Save DWORD to destination as null terminated 3 char string
stosd

;There is a byte overlap from the LODSD/STOSD, decrement source and destination by one byte
sub esi,1
sub edi,1

;Loop like this until ECX == 0
sub ecx,1
jnz @@ThreeCharLoop

;Get length of output string
mov edi,pOutput
xor eax,eax
or ecx,-1
repne scasb
add ecx,1
not ecx

;Set return value
mov eax,ecx
Ret

FormatString EndP


HR,
Ghandi

Thanks Ghandi.  :U

This is also very good, it is MASM without many external functions
or lib, and a good exercise to see what we can do with MASM instructions.

I've a lot to study now with 3 different samples of how we can do the
number formatting task.  :P

Quote from: Ghandi on May 06, 2010, 12:27:23 PM
Another thought also, you could eliminate the destination parameter and use stack memory to accommodate the input string, copy the input and then build the output string in the input buffer using the stack copy for reference. Vice versa, you could use the stack memory to hold the temp string and then once formatting has taken place you could copy the resulting string across to the input buffer.

HR,
Ghandi

Even more to study  :bg
Mind is like a parachute. You know what to do in order to use it :-)

jj2007

Quote from: frktons on May 06, 2010, 11:27:51 AM

So it's time to have a look at your masmbasic library  :P
For an old friend of BASIC dialects like me, it should be fine.

may I ask you if there are the asm sources of your lib
functions in order to study them?

Frank

Frank,
PM me your email address. Be warned, it's fairly complex stuff...