Trying to use the stack in order to insert thousand separator in a numeric string
I got this:
Quote
The original string: 3365417345
The reversed/formatted string: 543.714.563.3
The rereversed/formatted string: 3.365.417.345
Press any key to close...
It's funny and resource consuming.
So I'm going to experiment many possible alternative ways to do
the task:
inserting a thousand separator in a unsigned DWORD.
I think some dozen ways are possible.
Fell free to post any funny/alternative/classic way to get the job done.
Attached the
funny versionFrank
Frank,
What I did was broke the number into pieces of < 1000 (magic number multiply for divide by 1000), then converted each digit of the 999 by magic number multiply for divide by 10, then inserted the separator (if more preceeded but not yet converted), then divided by 1000 again... Stop when the dividend is 0. This gives you up to a 14 digit number (x,xxx,xxx,xxxNULL) which is Right justified in a 14 BYTE area. Left justify this by SSE unaligned load from the MSD (insure that the 14 BYTE work area is followed by 16 0's), then aligned/unaligned store depending on the alignment of the destination (only if necessary - you may just want to return the offset of the msd without moving).
Dave.
Quote from: KeepingRealBusy on September 20, 2010, 10:38:46 PM
Frank,
What I did was broke the number into pieces of < 1000 (magic number multiply for divide by 1000), then converted each digit of the 999 by magic number multiply for divide by 10, then inserted the separator (if more preceeded but not yet converted), then divided by 1000 again... Stop when the dividend is 0. This gives you up to a 14 digit number (x,xxx,xxx,xxxNULL) which is Right justified in a 14 BYTE area. Left justify this by SSE unaligned load from the MSD (insure that the 14 BYTE work area is followed by 16 0's), then aligned/unaligned store depending on the alignment of the destination (only if necessary - you may just want to return the offset of the msd without moving).
Dave.
Hi Dave,
Thanks for your explanations.
I posted something similar that is done in C and that doesn't need the last part
of realigning on the left the formatted string, maybe I'll slowly try to translate
it into masm. The only problem is that I have limited free time and Assembly knowledge.
By the way, if you don't mind, post the code as well, with a little example like
mine, to show how it works.
Or just add it to the code I posted.
In a few weeks a new testbed will come up with colors, boxes, grids, and a better
looking display for the results.
I'll use the routines that will be posted here to test the new testbed and to prepare it
for the future use on the forum, according to the comments and advices I get.
Frank
Frank,
Here is the function less the data definitions.
Dave.
;-------------------------------------------------------------------------------
;
; Convert a binary number to 14 decimal digits with separators.
; Enter with number pointer in edx (Lo/Hi), buffer pointer (16 digit) in edi.
; Return with number converted and edi pointing to MSD that is non-zero. The
; leading characters (in front of edi) are blanked, so you can write the 16
; digit, right justified, number at the original edi OFFSET, or only the
; significant digits using the returned edi OFFSET.
;
;-------------------------------------------------------------------------------
BTD PROC USES eax ebx ecx edx esi
mov eax,[edx] ; Get Low.
mov edx,[edx+4] ; Get High.
mov ebx,dBillion ; Get billion.
div ebx ; Convert to billions (eax) and fraction (edx).
mov dBillions,eax ; Save the billions.
mov eax,edx ; Convert the Low value.
;
;Get big memory block, size = 999,000,000,000 BYTES, the elapsed time is: 0.42
;
; Convert fraction then billions.
;
mov esi,eax ; Convert the Low value.
mov ecx,14 ; Set the character position for low 9 digits.
mov ebx,2
;
; Convert by multiplying.
;
Cvt:
mov edx,3435973837 ; Get magic number for divisor of 10.
mul edx ; edx = (quotient * 8) + garbage.
shr edx,3 ; edx = quotient
lea eax,[edx+edx*4] ; eax = quotient * 5.
shl eax,1 ; eax = quotient * 10.
neg eax ; eax = - quotient * 10.
lea eax,[esi+eax+'0'] ; eax = LSD
mov [edi+ecx],al ; Save digit.
mov eax,edx ; eax = quotient.
mov esi,eax ; Save quotient.
dec ecx ; Point to prior digit space.
dec ebx ; Decrement 1000's
jns Cnt ; Not there.
dec ecx ; Skip comma.
lea ebx,[ebx+3] ; Set for next 1000.
Cnt:
cmp ecx,2 ; Offset for the billions?
jg Cvt ; No, not done with Low conversion.
je GetHigh ; Yes, get the high value.
or ecx,ecx ; Total conversion complete?
jns Cvt ; No, keep converting High.
jmp Separate ; Yes.
GetHigh:
mov eax,dBillions ; Get billions value.
mov esi,eax ; Convert the High value 3 digits.
jmp Cvt
;
; Separate with commas.
;
Separate:
mov BYTE PTR [edi+3],','
mov BYTE PTR [edi+7],','
mov BYTE PTR [edi+11],','
jmp ScanNonZero ; Scan for decimal digit > 0
;
; Blank leading commas and leading zeros, not last zero.
;
BlankFill:
mov BYTE PTR [edi],' ' ; Blank the character.
inc edi ; Point to the next character.
ScanNonZero:
mov al,[edi] ; Get the character.
cmp al,',' ; Is it a leading comma?
jz BlankFill ; Yes, blank it.
cmp al,'0' ; Is it a leading '0'?
ja Exit ; No, a digit > '0'. done with blanking.
or al,al ; Is it a trailing null?
jnz BlankFill ; No, blank it.
dec edi ; Point to the last character.
mov BYTE PTR [edi],'0' ; Force the '0' back.
;
; Exit BTD.
;
Exit:
ret
BTD ENDP
Quote from: KeepingRealBusy on September 20, 2010, 11:30:20 PM
Frank,
Here is the function less the data definitions.
Dave.
;-------------------------------------------------------------------------------
;
; Convert a binary number to 14 decimal digits with separators.
; Enter with number pointer in edx (Lo/Hi), buffer pointer (16 digit) in edi.
; Return with number converted and edi pointing to MSD that is non-zero. The
; leading characters (in front of edi) are blanked, so you can write the 16
; digit, right justified, number at the original edi OFFSET, or only the
; significant digits using the returned edi OFFSET.
;
;-------------------------------------------------------------------------------
BTD PROC USES eax ebx ecx edx esi
mov eax,[edx] ; Get Low.
mov edx,[edx+4] ; Get High.
mov ebx,dBillion ; Get billion.
div ebx ; Convert to billions (eax) and fraction (edx).
mov dBillions,eax ; Save the billions.
mov eax,edx ; Convert the Low value.
;
;Get big memory block, size = 999,000,000,000 BYTES, the elapsed time is: 0.42
;
; Convert fraction then billions.
;
mov esi,eax ; Convert the Low value.
mov ecx,14 ; Set the character position for low 9 digits.
mov ebx,2
;
; Convert by multiplying.
;
Cvt:
mov edx,3435973837 ; Get magic number for divisor of 10.
mul edx ; edx = (quotient * 8) + garbage.
shr edx,3 ; edx = quotient
lea eax,[edx+edx*4] ; eax = quotient * 5.
shl eax,1 ; eax = quotient * 10.
neg eax ; eax = - quotient * 10.
lea eax,[esi+eax+'0'] ; eax = LSD
mov [edi+ecx],al ; Save digit.
mov eax,edx ; eax = quotient.
mov esi,eax ; Save quotient.
dec ecx ; Point to prior digit space.
dec ebx ; Decrement 1000's
jns Cnt ; Not there.
dec ecx ; Skip comma.
lea ebx,[ebx+3] ; Set for next 1000.
Cnt:
cmp ecx,2 ; Offset for the billions?
jg Cvt ; No, not done with Low conversion.
je GetHigh ; Yes, get the high value.
or ecx,ecx ; Total conversion complete?
jns Cvt ; No, keep converting High.
jmp Separate ; Yes.
GetHigh:
mov eax,dBillions ; Get billions value.
mov esi,eax ; Convert the High value 3 digits.
jmp Cvt
;
; Separate with commas.
;
Separate:
mov BYTE PTR [edi+3],','
mov BYTE PTR [edi+7],','
mov BYTE PTR [edi+11],','
jmp ScanNonZero ; Scan for decimal digit > 0
;
; Blank leading commas and leading zeros, not last zero.
;
BlankFill:
mov BYTE PTR [edi],' ' ; Blank the character.
inc edi ; Point to the next character.
ScanNonZero:
mov al,[edi] ; Get the character.
cmp al,',' ; Is it a leading comma?
jz BlankFill ; Yes, blank it.
cmp al,'0' ; Is it a leading '0'?
ja Exit ; No, a digit > '0'. done with blanking.
or al,al ; Is it a trailing null?
jnz BlankFill ; No, blank it.
dec edi ; Point to the last character.
mov BYTE PTR [edi],'0' ; Force the '0' back.
;
; Exit BTD.
;
Exit:
ret
BTD ENDP
Thanks Dave,
when the new testbed will be ready, if I need any explanation, I'll tell you.
And attached a second version using the API
GetNumberFormatFrank
And here we have a version Hutch posted some weeks ago.
The collection is quite big, stay tuned, I'll have to put them
all together and test them with the new testbed that is in the
design stage:
http://www.masm32.com/board/index.php?topic=14871.0
Frank
Frank, when you would implement your test-bed, then include the code for thousand separators also. This makes timings more readable. :U
Alex
Quote from: Antariy on September 21, 2010, 11:47:26 PM
Frank, when you would implement your test-bed, then include the code for thousand separators also. This makes timings more readable. :U
Alex
Of course, I need a good new CPUID code, the macro for timings, the thousand
separator routine, and so on.
Working on it,....slowly :lol
Frank
Quote from: frktons on September 21, 2010, 11:59:25 PM
Of course, I need a good new CPUID code, the macro for timings, the thousand
separator routine, and so on.
Working on it,....slowly :lol
Frank
I know too little peoples in forum, but I think, what Jochen can make very good macro for you, or explain how make it.
Alex
Quote from: Antariy on September 22, 2010, 12:09:08 AMJochen can make very good macro for you, or explain how make it.
My macros are for public use, no problem. Search the forum for CodeSize. However, I won't have the time to develop a thousand separators macro, that is pretty challenging - maybe Frank can volunteer? Besides, as for myself I prefer to use a
shl eax, 8 and call them kCycles.
Hi,
QuoteBesides, as for myself I prefer to use a shl eax, 8 and call them kCycles.
Well, perhaps formatting something like in the thread
"Re: decimal as KiB,Mib,GiB etc." in The Laboratry could be
used? See your reply #25 or mine #27 for example.
Regards,
Steve N.
Well, I'll have a look at the thread:
http://www.masm32.com/board/index.php?topic=9585.0
and see if there is something suitable for the actual task.
Frank
A version Alex sent to me:
;---------------------------------------------------------------------
; Thousand separator by Alex
;---------------------------------------------------------------------
.nolist
include \masm32\include\masm32rt.inc
.code
OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE
align 16
Separate1000ds proc STDCALL dwNum:DWORD, lpszOutStr:PTR DWORD
;int 3
pop edx ; ret
pop eax ; num
pop ecx ; str
push edx
push ebx
push esi
add esp,-(36)
mov esi,esp
push "ul0%"
push 1000T
mov ebx,esp
jmp @l1
@@:
div dword ptr [ebx]
push edx
mov word ptr [esi],"%." ; format and separator
mov dword ptr [esi+2],"ul30" ; format
add esi,6
@l1:
xor edx,edx
cmp eax,edx
jnz @B
add ebx,4
mov dword ptr [esi-6],edx
push ebx
push ecx
call wsprintf
lea esp,[ebx+40]
pop esi
pop ebx
retn
Separate1000ds endp
OPTION PROLOGUE:PrologueDef
OPTION EPILOGUE:EpilogueDef
align 16
start:
add esp,-1032
invoke crt_printf,repargof("Enter a value please: ")
invoke crt_scanf,repargof("%lu"),esp
test eax,eax
pop ecx
jnz @F
invoke crt_printf,repargof("The input doesn't seem to be a number")
jmp @done
@@:
invoke Separate1000ds,ecx,esp
invoke crt_printf,repargof("Thousand separated number: %s"),esp
@done:
invoke crt__getch
invoke crt_exit
end start
:U