Kodowanie znaków

Wstęp

Kodowanie jest formą reprezentacji znaków w formie elektronicznej. Obecnie dominującymi kodowaniami na świecie są UTF-8 oraz UTF-16. Są one różnym implementacjami standardu Unicode, który według założenia powinien obejmować wszystkie pisma na świecie. Czym jest zatem Unicode ? Jest to zestaw znaków, gdzie każdemu znakowi jest przypisany punkt kodu (ang. code point).

Przykładowy punkt kodu dla znaku ‘A’ to U+0041. Gdzie U+ stały sufix,  natomiast pozostała część pisana w systemie szesnastkowym, definiuje nam konkretny znak. Zakres jaki obejmuje standard Unicode to U+0000 U+10FFFF.

 

Co się stało z kodowaniem ASCII ?

Większość z nas pewnie zna odpowiedź na to pytanie. Kodowanie ASCII (ang. American Standard Code for Information Interchange) mieści zaledwie 7 bitów do przedstawienia znaku. Co za tym idzie ma ono jedynie 128 wolnych slotów na prezentację liter alfabetu angielskiego, cyfr, znaków przystankowych czy poleceń sterujących. Nie jest tego dużo, więc standard ASCII został wkrótce rozszerzony o 8 bit dzięki czemu zyskał kolejne 128 miejsc na wykorzystanie. Różne warianty wykorzystania tych miejsc zostały nazwane kodami stron. Np. kodowanie Win-1250 zawierało znaki dla wszystkich języków z krajów Europy  środkowej. Problem jednak pozostawał, gdyż tekst napisany w jednym obszarze świata nie mógł być odpowiednio odczytany w drugim miejscu używającego innego kodu strony.

UTF-8

Tak jak wspomniałem na wstępie każdy znak Unicode ma przypisany do siebie code point. Celem kodowania jest zapisanie tego znaku w odpowiednim formacie. Kodowanie UTF-8 charakteryzuje się tym, że znaki kodowane są na różnej ilości bajtów, od 1 do 6. Początkowe znaki Unicode są zgodne z kodowaniem ASCII, zatem ich reprezentacja w UTF-8 jest identyczna i jest umieszczona na 1 bajcie.

tabela UTF-8

żródło: wikipedia

 

Przyjrzyjmy się powyższej tabeli. Zapisując dane na jednym bajcie mamy do wykorzystania 7 bitów zamiast 8. Natomiast mając dwa bajty mamy do wykorzystania 11 bitów zamiast 16. Z kolei na 3 bajtach mamy jedynie 16 bitów. Wymusza to na nas algorytm który mówi, że pierwszy bity pierwszego bajtu mówią nam na ilu bajtach zapisany jest znak. Kolejne bajty mają natomiast się zaczynać od sekwencji bitów 10, informując nas o tym że jest to kontynuacja algorytmu kodowania.

Jak to wygląda w praktyce ?

Weźmy sobie grecki symbol małej lambdy λ, której przypisany jest code point U+03BB.

W systemie binarnym 0x03BB =  0000 0011 1011 1011

W tabeli powyżej widzimy że w przypadku code point’u z zakresu U+0080 do U+07FF zapisujemy 11 bitów z naszego kodu. Ucinamy więc pierwsze bity naszego binarnego zapisy aby zostało ich 11.

Przed 0000 0011 1011 1011
Po      xxxx  x011 1011 1011

Przepiszę teraz szablon dla naszego code point’u z powyższej tabeli, aby wszystko było bardziej wyraźne.

110xxxx 10xxxxxx

Następnie miejsca x kolejno zaczynając od lewej zastępuje kolejno bitami naszego ciągu składającego się z 11 bitów : 011 1011 1011.

Otrzymujemy:

11001110 10111011 = 0xCEBB

Tym sposobem zakodowaliśmy znak Unicode kodowaniem UTF-8.

UTF-16

Kodowanie UTF-16 charakteryzuje się tym, że znaki są tu zapsiane na 2 bądź 4 bajtach. Dla znaków z zakresu U+0000 – U+FFFF (wykluczając zakres U+D800  – U+DFFF) przy kodowaniu UTF-16 kod znaku zostaje taki sam jak punkt kodowy. Mała lambda λ , której przypisany jest code point U+03BB zostanie zapisana w postaci 03 BB.

Sprawa nieco się komplikuje gdy chcemy zakodować liczbę wykraczającą poza 0xFFFF.  W tym momencie do gry wchodzi algorytm z wykorzystaniem wspomnianego wcześniej wykluczonego zakresu U+D800  – U+DFFF,  do wyliczenia dwóch wartości określanych jako lead surrogate oraz trail surrogate.

Sposób kodowania zobrazuję na przykładzie przykładowego znaku,  którym będzie U+12345.

Algorytm wygląda następująco:

1)  Z racji, iż maksymalny zakres standardu Unicode to U+10FFFF, więc odejmując od naszego wybranego znaku stałą o wartości 0x10000, otrzymamy liczbę którą będzie można zapisać na 20 bitach.

Wykonajmy teraz te obliczenie:
0x12345 =     0001  0010  0011  0100  0101
–                       0001  0000  0000  0000  0000
__________________________________
                          0000 0010 0011 0100 0101

2)   Do górnych 10 bitów wyniku dodajemy stałą  0xD800.

0xD800 =     1101 0100 0000 0000
+                                  00 0000 1000
_______________________________
                      1101 0100 0000 1000 = 0xD808

Tym sposobem otrzymaliśmy pierwsze 2 bajty nazywane lead surrogate.

3)  Do dolnych 10 bitów wyniku z pkt 1 dodajemy stałą 0xDC00.

0xDC00 =     1101 1100 0000 0000
+                                 11  0100  0101
____________________________
                       1101 1111  0100  0101 = 0xDF45

Wykonując te obliczenie otrzymujemy kolejne 2 bajty nazywane trail surrogate.

4)  Gdy połączymy te dwa wyniki otrzymamy poprawnie zakodowany code point U+12345 w systemie UTF-16. Jest to D808 DF45.

Warto zwrócić na uwagę na poniższą tabelę

UTF-16 decoder

źródło: wikipedia

Przedstawia ona zakres ze standardu Unicode który nie zawiera code point’ów a jedynie pomaga nam w ich adresowaniu w przypadku zakresu U+10000 do U+10FFFF. Dzięki zabiegom dodawania stałych w pkt 2 oraz 3 otrzymujemy lead oraz trail surrogate. Każda z nich ma swój zakres, co widać w powyższe tabeli, poprzez łatwo je rozróżnić.

Podsumowując

To co przemawia za używaniem kodowania UTF nad ASCII to:

  • Uniocode wspiera znacznie szerszy zakres znaków
  • w każdej części świata implementacje standardu Unicode są odczytywane w ten sam sposób, dając zawsze te same znaki

 

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *