Autor: | Wojciech Muła |
---|---|
Dodany: | 3.05.2002 |
Aktualizacja: | 30.04.2008 |
Treść
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.