Tvůrce webu je i pro tebe! Postav třeba web. Bez grafika. Bez kodéra. Hned.
wz

Obsah / Utility / TEXTFORM / UIntToTextBuf

Zdrojový kód: INCLUDE\UTIL\TEXTFORM.INC, UTIL\TEXTFORM.ASM

Související:

UIntToTextBufN   Délka textu formátovaného čísla INT bez znaménka
FormToTextBuf   Zformátování textu do bufferu

UIntToTextBuf - Zformátování dekadického čísla bez znaménka do bufferu

Funkce UIntToTextBuf zkonvertuje 64-bitové celé číslo bez znaménka na text ve formátovaném dekadickém (desítkovém) tvaru.


; -----------------------------------------------------------------------------
;                  Convert unsigned INT number into text buffer
; -----------------------------------------------------------------------------
; INPUT:	EDX:EAX = number
;		EBX = formatting parameters FORMPAR
;		ECX = pointer to nationality descriptor NATIONAL
;		ESI = remaining free space in buffer
;		EDI = destination buffer
; OUTPUT:	ESI = next remaining free space in buffer
;		EDI = next destination buffer
; NOTES:	On AMD 1.6 GHz it takes approx. "10*digits+40" nanosecs for
;		0 to 10	digits, "50*digits-360" nanosecs for 10 to 20 digits.
; -----------------------------------------------------------------------------
; Local variables (ebp+N are read-only variables):
%define		UINTNumL   ebp+16	; (4) number LOW
%define		UINTNat    ebp+8	; (4) pointer to nationality NATIONAL
%define		UINTNumH   ebp+4	; (4) number HIGH
%define		UINTForm   ebp-4	; (4) formatting parameters FORMPAR
%define		UINTLen    ebp-8	; (4) text length
%define		UINT10Div  ebp-12	; (4) divider 10
					; (2)
%define		UINTThsnd  ebp-15	; (1) thousand separator
%define		UINTThsN   ebp-16	; (1) thousand separator counter

%define		UINTStack  16		; stack size

Na vstupu funkce obsahuje registrový pár EDX:EAX číslo bez znaménka ke konverzi, registr EBX obsahuje formátovací parametry FORMPAR (ne ukazatel), registr ECX ukazatel na popisovač národnostních informací NATIONAL, registr ESI čítač zbylého místa v cílovém bufferu a registr EDI ukazatel do cílového bufferu. Na výstupu z funkce jsou registry ESI a EDI posunuty na novou ukládací pozici.

Funkce používají lokální proměnné s bázovým registrem EBP. Proměnné s kladným offsetem (UINTNumL, UINTNat a UINTNumH) jsou použity jen pro čtení, jejich obsah není modifikován. UINTNumL je nižší dvojslovo vstupního čísla (vstupní registr EAX). UINTNat je ukazatel na národnostní informace NATIONAL (vstupní registr ECX). UINTNumH je vyšší dvojslovo vstupního čísla (vstupní registr EDX). UINTForm jsou formátovací parametry FORMPAR (vstupní registr EBX). UINTLen je délka ukládaného textu. UINT10Div je konstanta 10 použitá při dělení čísel. UINTThsnd je znak oddělovače tisíců. UINTThsN je čítač pro uložení oddělovače tisíců. UINTStack je velikost lokálních proměnných v zásobníku.


; ------------- Macro - decode and store one INT digit QWORD
; Uses: EDX:EBX=number, ESI=free space-1, EDI=end of destination, [UINT10Div]
; Destroys: EAX

%macro		INTTOTEXTBUFDIQ 0

		xor	eax,eax		; EAX <- 0
		xchg	eax,edx		; EAX <- number HIGH, EDX <- 0
		div	dword [UINT10Div] ; EAX <- number HIGH/10, EDX <- rest
		xchg	eax,ebx		; EBX <- number HIGH/10, EAX <- LOW
		div	dword [UINT10Div] ; EAX <- new LOW, EDX <- number % 10
		inc	esi		; shift free space counter
		xchg	eax,edx		; EDX <- new LOW, EAX <- number % 10
		jle	%%L1		; position is behind end of buffer
		add	al,"0"		; AL <- convert to ASCII character
		mov	[edi],al	; store character
%%L1:		xchg	edx,ebx		; EBX <- new LOW, EDX <- new HIGH
		dec	edi		; shift destination pointer
%endmacro

Pomocné makro INTTOTEXTBUFDIQ je používáno uvnitř funkce k uložení jedné číslice v případě, že číslo má rozměr QWORD (2 čtyřslova). Makro modifikuje vstupní číslo v registrovém páru EDX:EBX. V registru ESI je čítač volného místa v cílovém bufferu snížený o 1. EDI je ukazatel konce dat v cílovém bufferu (číslo se ukládá odzadu). Obsah registru EAX se funkcí zničí.

Z čísla v registrech EDX:EBX je vypočtena další číslice postupným dělením - nejdříve je vyděleno vyšší dvojslovo čísla číslem 10, nové vyšší slovo se uschová do registru EBX a dělí se nižší dvojslovo (zbytek po dělení vyššího slova zůstal v registru EDX).

Inkrementací čítače volného místa v registru ESI se provede test, zda je ukazatel do cílového bufferu platný. Není-li čítač kladný, ukazuje ukazatel za konec cílového bufferu, v tom případě se přeskočí část ukládající další znak.

Přičtením ASCII znaku "0" ke zbytku dělení v registru AL se číslo zkonvertuje na ASCII číslici, která se uloží do cílového bufferu. Do registru EDX se uloží vyšší dvojslovo nového čísla, do registru EBX nižší dvojslovo a ukazatel cílového bufferu se posune.


; ------------- Macro - decode and store one INT digit DWORD
; Uses: EBX=number, ESI=free space-1, EDI=end of destination
; Destroys: EAX, EDX

%macro		INTTOTEXTBUFDID 0

		mov	eax,3435973837	; EAX <- 800000000h/10, rounded up
		mul	ebx		; EDX <- (number*8) / 10
		shr	edx,3		; EDX <- number / 10
		mov	al,10		; AL <- 10
		mul	dl		; AL <- number / 10 * 10 low byte
		sub	bl,al		; BL <- number % 10
		inc	esi		; shift free space counter
		jle	%%L1		; position is behind end of buffer
		add	bl,"0"		; BL <- convert to ASCII character
		mov	[edi],bl	; store character
%%L1:		xchg	edx,ebx		; EBX <- number / 10
		dec	edi		; shift destination pointer
%endmacro

Pomocné makro INTTOTEXTBUFDID je používáno uvnitř funkce k uložení jedné číslice v případě, že číslo má rozměr DWORD (1 čtyřslovo). Na rozdíl od předchozího makra se používá operace násobení a výpočet je rychlejší. Makro modifikuje vstupní číslo v registru EBX. V registru ESI je čítač volného místa v cílovém bufferu snížený o 1. EDI je ukazatel konce dat v cílovém bufferu (číslo se ukládá odzadu). Obsah registrů EAX a EDX se funkcí zničí.

Z čísla v registru EBX bude vypočtena další číslice dělením deseti - avšak namísto dělení se použije rychlejší operace násobení převrácenou hodnotou čísla. Po vydělení registru EDX osmi bude obsahovat registr EDX podíl po dělení čísla deseti. Nyní je třeba získat ještě zbytek po dělení. Ten vznikne zpětným vynásobením podílu desetu a odečtením výsledku od původního čísla. Vzhledem k velikosti zbytku (0 až 9) však stačí operace provést pouze s posledním bajtem čísla, výsledek bude v registru BL.

Inkrementací čítače volného místa v registru ESI se provede test, zda je ukazatel do cílového bufferu platný. Není-li čítač kladný, ukazuje ukazatel za konec cílového bufferu, v tom případě se přeskočí část ukládající další znak.

Přičtením ASCII znaku "0" ke zbytku dělení v registru BL se číslo zkonvertuje na ASCII číslici, která se uloží do cílového bufferu. Do registru EBX se uloží nové číslo a ukazatel cílového bufferu se posune.


; ------------- Macro - store thousand separator
; Uses: ESI=free space-1, EDI=end of destination, ECX=number of characters,
; [UINTThsN], [UINTThsnd], Destroys: AL

%macro		INTTOTEXTBUFSEP 0

		dec	byte [UINTThsN]	; count order counter
		jnz	%%L2		; no thousand separator
		inc	esi		; shift free space counter
		jle	%%L1		; position is behind end of buffer
		mov	al,[UINTThsnd]	; AL <- thousand separator
		mov	[edi],al	; store character
%%L1:		dec	edi		; shift destination pointer
		dec	ecx		; decrease character counter
		mov	byte [UINTThsN],3 ; init order counter
		jz	UIntToTextBuf8	; no next character
%%L2:

%endmacro

Pomocné makro INTTOTEXTBUFSEP je používáno uvnitř funkce k uložení znaku oddělovače řádů. V registru ESI je čítač volného místa v cílovém bufferu snížený o 1. EDI je ukazatel konce dat v cílovém bufferu (číslo se ukládá odzadu). Registr ECX obsahuje čítač znaků k uložení.

Dekrementací lokální proměnné UINTThsN se čítá počet znaků do příštího oddělovače řádů. Není-li čítač nulový, přeskočí se obsluha uložení oddělovače řádů. Inkrementací čítače volného místa v registru ESI se provede test, zda je ukazatel do cílového bufferu platný. Není-li čítač kladný, ukazuje ukazatel za konec cílového bufferu, v tom případě se znak neuloží, pouze se posune ukazatel cílových dat. Má-li být znak uložen, načte se z lokální proměnné UINTThsnd znak oddělovače řádů a uloží se do bufferu.

Ukazatel cílových dat EDI se dekrementuje na novou pozici v textu, stejně tak čítač zbylých znaků v ECX se dekrementuje. Do lokální proměnné UINTThsN se připravý nový čítač řádů.


; ------------- Check remaining free space in buffer

UIntToTextBuf:	or	esi,esi		; check remaining space
		jle	short UIntToTextBufN9 ; not enough free space

; ------------- Push registers (it must agree with IntToTextBuf)

		push	eax		; push EAX number LOW
		push	ebx		; push EBX
		push	ecx		; push ECX pointer to nationality
		push	edx		; push EDX number HIGH
		push	ebp		; push EBP
		mov	ebp,esp		; EBP <- push ESP
		sub	esp,UINTStack	; ESP <- create local variables

; ------------- Prepare local variables

		mov	[UINTForm],ebx	; formatting parameters FORMPAR

Na začátku funkce UIntToTextBuf je nejdříve proveden test volného místa v cílovém bufferu. Není-li v cílovém bufferu volné místo, funkce se ihned ukončí.

Po úschově registrů se vytvoří v zásobníku místo pro lokální proměnné.Do proměnné UINTForm se uschovají formátovací parametry z registru EBX.


; ------------- Get text length without spaces (-> EDX)

		call	UInt0ToTextBufN	; get text length without spaces
		mov	[UINTLen],eax	; store text length
		xchg	eax,edx		; EDX <- push text length

Ke zformátování čísla v bufferu je potřeba nejdříve znát délku čísla. Délka čísla se zjistí funkcí UInt0ToTextBufN, která na rozdíl od funkce UIntToTextBufN ignoruje parametr minimální šířky pole s údajem. Délka čísla se uchová do proměnné UINTLen a do registru EDX.


; ------------- Store leading spaces

		bt	ebx,FORMFLAG_Left_b ; left-justify?
		jc	UIntToTextBuf3	; left-justify, no leading spaces
		movzx	ecx,bh		; ECX <- minimal width of field
		sub	ecx,edx		; ECX <- remaining spaces
		jle	UIntToTextBuf3	; no spaces left
		bt	ebx,FORMFLAG_Cent_b ; center?
		jnc	UIntToTextBuf2	; no, right-justify
		shr	ecx,1		; ECX <- spaces / 2, round down
		jz	UIntToTextBuf3	; no spaces left
UIntToTextBuf2:	mov	al," "		; AL <- space
UIntToTextBuf21:dec	esi		; decrease space counter
		stosb			; store one space
		jz	UIntToTextBuf9	; buffer is full
		loop	UIntToTextBuf21	; next character

Není-li číslo zarovnáno doleva (příznakový bit FORMFLAG_Left_b), je nutno před číslo doplnit úvodní mezery. Do registru ECX se připraví z registru BH (což je parametr FORMPAR_Width z popisovače formátovacích parametrů) požadovaná šířka pole. Odečtením délky textu z registru EDX se obdrží zbývající šířka pro okraje. Je-li to kladné číslo, bude se pokračovat dále. Má-li být text centrován, použije se pouze polovina mezer se zaokrouhlením dolů.

V cyklu se ukládají znaky mezer do cílového bufferu. Počet mezer je v registru ECX. Dekrementací registru ESI se ověřuje, zda bude v bufferu místo pro další znak. Pokud čítač ESI dosáhne nuly, dosáhlo se konce cílového bufferu a funkce se ukončí.


; ------------- Prepare registers to convert number (here jumps IntToTextBuf)

UIntToTextBuf3:	mov	ecx,edx		; ECX <- length in characters
		mov	ebx,[UINTNumL]	; EBX <- number LOW
UIntToTextBuf32:add	edi,ecx		; EDI <- last character + 1
		mov	edx,[UINTNumH]	; EDX <- number HIGH
		sub	esi,ecx		; ESI <- subtract length
		or	ecx,ecx		; check text length
		jle	UIntToTextBuf81	; no characters

; ------------- Push registers 2

		push	esi		; push ESI
		push	edi		; push EDI
		dec	edi		; EDI <- set pointer to last character

Při přípravě registrů ke konverzi se do dvojregistru EDX:EBX načte konvertované číslo. Přičtením zbývající délky textu čísla k EDI a odečtením od ESI se ukládací ukazatel posune za konec ukládaného textu. Není-li délka textu v registru ECX platné kladné číslo, přeskočí se část pro ukládání čísla.

Po úschově zápisových ukazatelů ESI a EDI se dekrementací EDI nastaví ukládací ukazatel na poslední znak čísla.


; ------------- Convert number without thousand separator
; EDX:EBX=number, ESI=free space-1, EDI=end of destination, ECX=num. of chars

		bt	dword [UINTForm],FORMFLAG_Thsn_b; thousand separator?
		jc	UIntToTextBuf5	; use thousand separator

; ------------- Prepare registers 2

UIntToTextBuf4:	push	ebp		; push EBP
		mov	ebp,10		; EBP <- 10, divider

; ------------- Check if number is DWORD

UIntToTextBuf42:or	edx,edx		; is number QWORD?
		jz	UIntToTextBuf44	; number is DWORD

; ------------- Decode digits as QWORD without separator

		xor	eax,eax		; EAX <- 0
		xchg	eax,edx		; EAX <- number HIGH, EDX <- 0
		div	ebp		; EAX <- number HIGH/10, EDX <- rest
		xchg	eax,ebx		; EBX <- number HIGH/10, EAX <- LOW
		div	ebp		; EAX <- new LOW, EDX <- number % 10
		inc	esi		; shift free space counter
		xchg	eax,edx		; EDX <- new LOW, EAX <- number % 10
		jle	UIntToTextBuf43	; position is behind end of buffer
		add	al,"0"		; AL <- convert to ASCII character
		mov	[edi],al	; store character
UIntToTextBuf43:xchg	edx,ebx		; EBX <- new LOW, EDX <- new HIGH
		dec	edi		; shift destination pointer
		loop	UIntToTextBuf42	; next digit
		pop	ebp		; pop EBP
		jmp	UIntToTextBuf8

Je-li nastaven příznakový bit FORMFLAG_Thsn_b, bude se číslo dekódovat s oddělovači řádů. Jinak se pokračuje dekódováním bez oddělovačů řádů. Obsah registru EBP se uchová a připraví se do něj dělící konstanta 10.

Je-li registr EDX nulový, bude set číslo dekódovat s velikostí DWORD, jinak se pokračuje dekódováním čísla pomalejší operací s dělením jako QWORD.

Během konverze se modifikuje vstupní číslo v registrovém páru EDX:EBX. V registru ESI je čítač volného místa v cílovém bufferu snížený o 1. EDI je ukazatel konce dat v cílovém bufferu (číslo se ukládá odzadu). Registr ECX obsahuje čítač číslic a registr EBP konstantu 10.

Z čísla v registrech EDX:EBX je vypočtena další číslice postupným dělením - nejdříve je vyděleno vyšší dvojslovo čísla číslem 10, nové vyšší slovo se uschová do registru EBX a dělí se nižší dvojslovo (zbytek po dělení vyššího slova zůstal v registru EDX).

Inkrementací čítače volného místa v registru ESI se provede test, zda je ukazatel do cílového bufferu platný. Není-li čítač kladný, ukazuje ukazatel za konec cílového bufferu, v tom případě se přeskočí část ukládající další znak.

Přičtením ASCII znaku "0" ke zbytku dělení v registru AL se číslo zkonvertuje na ASCII číslici, která se uloží do cílového bufferu. Do registru EDX se uloží vyšší dvojslovo nového čísla, do registru EBX nižší dvojslovo a ukazatel cílového bufferu se posune.

Čítáním registru ECX se operace opakuje pro další číslici. Po ukončení konverze se obnoví obsah registru EBP.


; ------------- Decode digits as DWORD without separator

UIntToTextBuf44:INTTOTEXTBUFDID		; decode and store one INT digit DWORD
		loop	UIntToTextBuf44	; next digit
		pop	ebp		; pop EBP
		jmp	UIntToTextBuf8

Dosáhne-li číslo rozměru 1 DWORD, pokračuje se dále dekódováním čísla rychlejší operací násobení s využitím makra INTTOTEXTBUFDID. Opět se čítáním registru ECX operace opakuje pro další číslici. Po ukončení konverze se obnoví obsah registru EBP.


; ------------- Prepare to convert number with thousand separator
; EDX:EBX=number, ESI=free space-1, EDI=end of destination, ECX=num. of chars

UIntToTextBuf5:	mov	eax,[UINTNat]	; EAX <- pointer to nationality
		mov	dword [UINT10Div],10 ; prepare divider
		mov	al,[eax+NAT_ThsndSep] ; AL <- thousand separator
		mov	byte [UINTThsN],4 ; prepare thousand separator counter
		mov	[UINTThsnd],al	; prepare thousand separator

; ------------- Check if number is DWORD

UIntToTextBuf51:or	edx,edx		; is number QWORD?
		jz	UIntToTextBuf55	; number is DWORD

; ------------- Separator

		INTTOTEXTBUFSEP		; store thousand separator

; ------------- Decode digits as QWORD

		INTTOTEXTBUFDIQ		; decode and store one INT digit QWORD
		loop	UIntToTextBuf51	; next digit
		jmp	UIntToTextBuf8

; ------------- Separator

UIntToTextBuf55:INTTOTEXTBUFSEP		; store thousand separator

; ------------- Decode digits as DWORD

		INTTOTEXTBUFDID		; decode and store one INT digit DWORD
		loop	UIntToTextBuf55	; next digit

Je-li nastaven příznakový bit FORMFLAG_Thsn_b, bude se číslo dekódovat s oddělovači řádů. Během konverze obsahuje registrový pár EDX:EBX konvertované číslo, registr ESI čítač volného místa - 1, registr EDI ukazatel na konec ukládaného textu a registr ECX čítač znaků k uložení.

Do lokální proměnné UINT10Div se připraví dělící konstanta 10. Ze struktury popisovače národnostních informací se připraví do proměnné UINTThsnd znak oddělovače řádů a do proměnné UINTThsN se připraví čítač řádů.

Je-li registr EDX nulový, bude set číslo dekódovat s velikostí DWORD, jinak se pokračuje dekódováním čísla pomalejší operací s dělením jako QWORD.

Během dekódování čísla jako QWORD se pomocí makra INTTOTEXTBUFSEP uloží nejdříve oddělovač tisíců a poté se pomocí makra INTTOTEXTBUFDIQ dekóduje jedna číslice. Čítáním registru ECX se opakuje operace dekódování pro jednu číslici.

Podobně během dekódování jako DWORD se pomocí makra INTTOTEXTBUFSEP uloží nejdříve oddělovač tisíců a poté se pomocí makra INTTOTEXTBUFDID dekóduje jedna číslice. Čítáním registru ECX se opakuje operace dekódování pro jednu číslici.


; ------------- Pop registers 2

UIntToTextBuf8:	pop	edi		; pop EDI
		pop	esi		; pop ESI

; ------------- Limit offset

UIntToTextBuf81:or	esi,esi		; buffer overflow?
		jg	UIntToTextBuf82	; buffer is OK
		add	edi,esi		; EDI <- correct address
		xor	esi,esi		; limit free space to 0
		jmp	short UIntToTextBuf9

Po dekódování textu čísla se navrátí ukládací registry ESI a EDI, které ukazovaly za konec ukládaného čísla. Ukazatele se upraví tak, aby nepřesahovaly konec bufferu - tj. přesahující ukládací ukazatel EDI se přičtením čítače ESI posune na konec a poté se čítač zbylého místa vynuluje.


; ------------- Store trailing spaces

UIntToTextBuf82:mov	eax,[UINTLen]	; EAX <- text length
		movzx	ecx,byte [UINTForm+FORMPAR_Width] ; ECX <- min. width
		sub	ecx,eax		; ECX <- remaining spaces
		jle	UIntToTextBuf9	; no spaces remain
		test	byte [UINTForm+FORMPAR_Flags1],FORMFLAG1_Left ; left?
		jnz	UIntToTextBuf84	; left-justify
		test	byte [UINTForm+FORMPAR_Flags2],FORMFLAG2_Cent ; center?
		jz	UIntToTextBuf9	; no center
		shr	ecx,1		; ECX <- spaces / 2
		adc	cl,ch		; ECX <- round up
		jz	UIntToTextBuf9	; no spaces
UIntToTextBuf84:mov	al," "		; AL <- space
UIntToTextBuf85:stosb			; store one space
		dec	esi		; decrease space counter
		loopnz	UIntToTextBuf85	; next character

; ------------- Pop registers

UIntToTextBuf9:	mov	esp,ebp		; pop ESP
		pop	ebp		; pop EBP
		pop	edx		; pop EDX
		pop	ecx		; pop ECX
		pop	ebx		; pop EBX
		pop	eax		; pop EAX
		ret

Poslední operací je uložení mezer za koncem čísla. Do registru EAX je načtena délka textu (bez mezer), do registru ECX požadovaná šířka pole, odečtením zůstane v registru ECX počet zbývajících mezer. Pokud žádné mezery pro okraje nezbyly, funkce se ukončí.

Je-li nastaven příznakový bit FORMFLAG1_Left, je číslo zarovnáno doleva, ihned se přejde k ukládání mezer za číslo. Není-li tento příznak nastaven a není-li nastaven ani příznakový bit FORMFLAG2_Cent, bylo číslo zarovnáno doprava a mezery tedy již byly uloženy dříve, takže se funkce ukončí. Jinak platí, že číslo je centrováno. Do registru ECX se připraví poloviční počet mezer, ale tentokrát se zaokrouhlí nahoru (aby se zachytila případná lichá mezera).

Následuje ukládání mezer do bufferu. Počet mezer je dán čítačem v registru ECX. Současně se sleduje zbývající volné místo v cílovém bufferu čítáním registru ESI. Pokud dosáhne nuly, bylo dosaženo konce bufferu a smyčka se ukončí.


Obsah / Utility / TEXTFORM / UIntToTextBuf