| Autor: | Wojciech Muła |
|---|---|
| Dodany: | 3.05.2002 |
| Aktualizacja: | 30.04.2008 |
Contents
Poniższe programy zamieniają liczbę zapisaną w rejestrze AL, na reprezentację binarną w łańcuchu ASCII-Z.
; wejście: al
; wyjście: _string (zakończony zerem)
segment .data
_string db "????????", 0
segment .text
mov cx, 8 ; 8-bitów do przetworzenia
mov di, _string ; offset łańcucha
_conv:
mov bl, 0 ; bl = 0
rol al, 1 ; CF = MSB(al)
adc bl, '0' ; bl = ascii(CF)
mov [di], bl ; [di++] = bl
inc di ;
loop _conv
; wejście: al
; wyjście: mm0
segment .data
mmx_bits db 0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01 ; maska dla kolejnych bitów
mmx_asc0 db 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30 ; 0x30 = ascii('0')
_string db "????????",0
segment .text
; np. a = 0xe9 (11101001b)
movd mm1, eax ; mm1 = |00|00|00|00|xx|xx|xx|e9|
punpcklbw mm1, mm1 ; mm1 = |00|00|00|00|xx|xx|e9|e9|
punpcklbw mm1, mm1 ; mm1 = |xx|xx|xx|xx|e9|e9|e9|e9|
punpcklbw mm1, mm1 ; mm1 = |e9|e9|e9|e9|e9|e9|e9|e9|
; zostaw pojedyncze bity w każdym bajcie
pand mm1, [mmx_bits] ; mm1 = |01|00|00|01|00|01|01|01|
pcmpeqb mm1, [mmx_bits] ; mm1 = |ff|00|00|ff|00|ff|ff|ff|
movq mm0, [mmx_asc0] ; mm0 = |30|30|30|30|30|30|30|30|
psubb mm0, mm1 ; mm0 = |31|30|30|31|30|31|31|31| = "11101001"
movq [_string], mm0
By wyświetlić liczbę, należy odczytać poszczególne cyfry. Najprościej jest pobrać najmłodszą cyfrę — jest to reszta z dzielenia przez 10 (ogólnie: przez podstawę systemu). Z kolei wynikiem dzielenia liczby przez wagę najstarszej cyfry jest właśnie najstarsza cyfra. Generalnie, odczyt dowolnej cyfry przedstawia się następująco.
// x - liczba (np. 10506)
// digit_pos - pozycja cyfry (>0) ( 2)
// base - podstawa systemu liczenia ( 10)
int get_digit(int x, int digit_pos, int base=10) {
// x = 10506
x /= pow(base, digit_pos) // x = 105
x %= base // x = 5
return x;
}
Wadą tego rozwiązania jest uzyskiwanie cyfr w odwrotnej kolejności (od najmłodszej do najstarszej), ale można to rozwiązać na co najmniej dwa sposoby:
Zwykłe odwracanie łańcucha jest oczywiście dość proste, ale ja proponuję inne podejście. Otóż można użyć stosu do niejawnego odwrócenia łańcucha, a właściwie do odwrócenia kolejności zapisów do łańcucha.
void display_uint(unsigned int x) {
if (!x) return
display_uint(x/10);
// wyświetlenie cyfry
putchar(x % 10 + '0');
}
Przy rekursji stos jest wykorzystywany zupełnie naturalnie, ale można również bezpośrednio użyć struktury stosowej.
void push(char);
char pop();
int stack_empty();
void display_uint(unsigned int x)
{
do {
push(x%10 + '0');
x /= 10;
}
while (x)
while (!stack_empty())
putchar(pop());
}
W asemblerze użycie powyższego sposobu jest nadzwyczaj proste, ponieważ procesor wspiera operacje na stosie poprzez instrukcje push i pop.
; edi - wskaźnik na łańcuch - minimum 11 bajtów
; eax - zamieniana wartość (bez znaku!)
dword2dec_ascii:
pushad
mov ecx, 10
mov ebp, esp ; zapisz wierzchołek stosu
.conv:
xor edx, edx ; edx = eax % 10
div ecx ;
lea ebx, [edx + '0'] ; ebx = ASCII(edx)
push bx
or eax, eax ; do ... while (eax)
jnz .conv ; /
cld
.create_string:
cmp ebp, esp ; while(!stack_empty()) - dopóki
je .end ; oryginalna zawartość stosu
; nie zostanie odtworzona
pop ax ; stos jest opróżniany
stosb ; a zapisane na nim uprzednio litery
; zapisywane do łańcucha [edi]
jmp .create_string
.end:
mov [edi], byte 0 ; zapisz kończący symbol
popad
ret
W tym przypadku łańcuch wypełnia się od końca, a funkcja zwraca wskaźnik do ostatnio zapisanego znaku.
#define size 20 /* ilość cyfr */
char string[size+1] = {0};
char* uint2ascii(unsigned int x)
{
char *c = &string[size-1]; // przedostatni znak
do
{
*c-- = x % 10 + '0';
x /= 10;
}
while (x)
return c;
}
W asemblerze kod jest równie prosty, a nawet bardziej, bowiem rozkaz div zarówno dzieli jak i liczy resztę. W C zdefiniowano funkcję div, która działa analogicznie do rozkazu div.
; esi = c > string[size]
; eax = x
uint2ascii:
mov ebx, 10
.conv:
div eax, ebx ; eax = eax/10
; edx = eax%10
add dl , '0' ; dl = ASCII(dl)
mov [esi], dl
dec esi
test eax, eax ; if (x != 0) goto .conv
jnz .conv
ret
Jest to czasochłonna metoda, jednak dość ciekawa. Za darmo można uzyskać zera wiodące (ang. leading zeros), co być może w niektórych zastosowaniach się przyda, aczkolwiek wymaga większej liczby obliczeń.
void display_uint(unsigned int x)
{ // 4294967295 -- maksymalna wartość dword'a
unsigned int weight = 1000000000;
do {
putchar(x/weight + '0'); // wyświetlenie najstarszej cyfry
x %= weight; // "wycięcie" najstarszej cyfry
weight /= 10; // następna cyfra
}
while (x);
}
Funkcje konwertujące liczby na system szesnastkowy.
Najprostszym sposobem jest tablicowanie cyfr szesnastkowych:
segment .data
hax_digits db "0123456789abcdef"
string db "????????", 0 ; null terminated string
segment .text
dword2hex:
; eax - liczba do wyświetlania
mov edi, string+8 ; offset ostatniego znaku
mov ecx, 8 ; ilość tetrad (ang. nibbles)
; tetrada to 4 kolejne bity, czyli tyle ile wymaga do
; zapisania 1 cyfra szesnastkowa
xor ebx, ebx
_conv:
mov bl, al ; eax = |hg|fe|dc|ba|
and bl, 0x0f ; ebx = |00|00|00|0a|
mov bl, [hex_digits+ebx] ; bl = 'a';
mov [edi], bl ; zapisz znak
dec edi
shr eax, 4 ; eax = |0h|gf|ed|cb| -- następna tetrada
loop _conv
Można obyć się bez tablicy; proszę przyjrzeć się kodom ASCII poszczególnych liter:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f |
| 0x30 | 0x31 | 0x32 | 0x33 | 0x34 | 0x35 | 0x36 | 0x37 | 0x38 | 0x39 | 0x61 | 0x62 | 0x63 | 0x64 | 0x65 | 0x66 |
Wybór litery jest prosty:
char hex_digit(unsigned int nibble)
{
if (nibble > 0xa) return = nibble + '0';
else return = (nibble-10) + 'a';
}
; 2-02-02
; wejście: al - tetrada
; wyjście: bl - bl='0' + (al >= 9) ? al : al+('a'-10-'0');
; niszczy: cl
mov bl, '0'
mov cl, 9
cmp cl, al ; CF = al>cl
sbb cl, cl ; cl = -1 jeśli CF==1
and cl, 'a'-10-'0'
add bl, al
add bl, cl
segment .text
_word2ascii_mmx:
; eax - liczba ; eax: hgfedcba
; Pierwszym krokiem jest odwrócenie kolejność tetrad,
; ponieważ pierwszy jest wyświetlany bajt o młodszym adresie, a
; na pierwszej pozycji jest zawsze najstarsza cyfra.
; Stąd całe zamieszanie
bswap eax ; eax: badcfehg
; Kolejnym krokiem jest "rozpakowanie" tetrad do
; bajtów
movd mm0, eax ; mm0: 00000000badcfehg
movd mm1, eax ; mm1: 00000000badcfehg
psrlq mm0, 4 ; mm0: 000000000badcfeh
punpcklbw mm0, mm1 ; mm0: ba 0b dc ad fe cf hg eh
pand mm0, [mm_mask] ; mm0: 0a 0b 0c 0d 0e 0f 0g 0h
; Na końcu właściwa konwersja
; np.
movq mm1, mm0 ; mm1: 0a 01 07 0f 0d 00 08 0c
pcmpgtb mm1, [mm_nine] ; mm1: ff 00 00 ff ff 00 00 ff ; mm1 > 9
pand mm1, [mm_corr] ; mm2: 27 00 00 27 27 00 00 27
paddb mm0, [mm_zero] ;
paddb mm0, mm1 ;
movq [string], mm0
ret;urn
segment .data
mm_mask dd 0x0f0f0f0f, 0x0f0f0f0f ; maska dla młodszych tetrad
mm_nine dd 0x09090909, 0x09090909 ; packed_byte(0x9)
mm_zero dd 0x30303030, 0x30303030 ; packed_byte('0')
mm_corr dd 0x27272727, 0x27272727 ; packed_byte('a' - 10)
string db '????????',0
Zobacz wpis w osobnym artykule.