Uvod
Everyone in the world should be able to use their own language on phones and computers.
Citat koji ste prethodno pročitali, preuzet je sa zvaničnog sajta konzorcijuma UNICODE i potcrtava osnovnu ideju koja stoji iza pomenutog standarda koji (baš kao što je navedeno), korisnicima najrazličitijih računarskih sistema već duže vreme omogućava, da - na različitim uređajima, na različitim operativnim sistemima i u različitim programima - koriste maternji jezik (uz odgovarajuće pismo), ili bilo koji drugi jezik.
Reklo bi se da je 'nekako red', da početkom treće decenije dvadesetog veka bude tako kako smo naveli, ali - situacija nije oduvek bila povoljna.
Nekada su "nad mutnom vodom graktale vrane" .....
Uvođenje ASCII standarda
Pre nego što je postalo moguće na računarima koristiti "sve" * znakove iz bilo kog jezika, na red su prvo došli znakovi engleskog alfabeta (uz cifre, znakove interpunkcije, znakove osnovnih matematičkih operacija i određeni broj drugih uobičajenijih znakova).
Međutim, bilo je potrebno uspostaviti (pre svega), osnovni sistem koji uopšte omogućava bilo kakvo korišćenje znakova na računarima (a ne brinite, osvrnućemo se nešto kasnije i na vrane).
Da bismo vam malčice "zakomplikovali život" (zapravo, da bismo vam pomogli da što bolje razumete tematiku članka), pre nego što pređemo na ASCII i UNICODE - osvrnućemo se na osnovni princip zapisivanja znakova na računarima.
Kako računari (uopšte) mogu razumeti znakove?
U članku u kome smo diskutovali o tome zašto binarni brojevni sistem (a ne dekadni), predstavlja matematičku osnovu računarskih sistema, objasnili smo da računari - ne samo da nisu u stanju da neposredno razumeju dekadni brojevni sistem, već da (u opštem smislu), nisu u stanju da neposredno razumeju ni brojeve (odnosno, da su u stanju da 'razumeju' samo naponska stanja u elektronskim kolima).
Ako računari nisu u stanju da neposredno razumeju (čak ni binarne) brojeve, nije za očekivati ni da budu u stanju da neposredno razumeju znakove?!
Međutim (u praktičnom smislu), ukoliko su logička kola u računarima u stanju da prepoznaju dva različita naponska stanja: "nema napona" i "ima napona" (a logička kola u računarima - jesu sposobna za takvo raspoznavanje), navedena naponska stanja se mogu shvatiti i kao cifre binarnog brojevnog sistema: 0 i 1, a nule i jedinice se dalje mogu nizati tako da tvore binarne brojeve (koji se kasnije, 'ako zatreba', mogu interpretirati tako da korisnicima budu prikazani kao dekadni brojevi).
Nizanjem bitova (ili, da budemo precizniji - nizanjem bajtova), takođe se mogu kodirati i znakovi.
Osnovna ideja za kodiranje znakova (makar u okviru jednog računarskog sistema), podrazumeva da se svaki znak povezuje sa unapred izabranom brojčanom vrednošću (uvek istom), a kada se navedena ideja uspešno implementira u okviru jednog računarskog sistema, ostaje "samo" pitanje - kako uskladiti više različitih računarskih sistema.
U prvim godinama razvoja računarske industrije (druga polovina 50-ih godina 20. veka i veći deo 60-ih godina), odnosno, pre uvođenja ASCII standarda, zapis znakova na različitim računarskim sistemima - nije bio usklađen.
Različite, raznolike i udaljene računare, odlikovali su idiosinkratični sistemi za zapis znakova (idiosinkratičan = sebi svojstven), pa stoga - reklo bi se krajnje očekivano - nije postojala ni mogućnost razmene tekstualnih poruka.
U navedenom periodu, dok su (u kontekstu šire primene), računari još uvek delovali kao uređaji iz domena naučne fantastike, nemogućnost međusobne komunikacije računara nije predstavljala preveliki problem (recimo da je donekle tako), ali, razmišljalo se i o budućnosti - za koju se još onomad očekivalo da će podrazumevati širu rasprostranjenost računara i veći obim komunikacije između računara, i zaključeno je da takva budućnost neće biti moguća bez (pre svega), opšteprihvaćenog standarda za razmenu tekstualnih poruka između različitih računarskih sistema.
Pojava ASCII standarda i osnovne odrednice
ASCII (American Standard Code for Information Interchange), prvi je široko rasprostranjen i opšteprihvaćen standard za kodiranje znakova u elektronskoj komunikaciji i, kao takav, hardverski podržan od većine računara koji su se u međuvremenu pojavili (koliko nam je poznato - skoro svih).
Prva specifikacija ASCII standarda objavljena je 1963, ali je ASCII svoj krajnji oblik poprimio krajem šezdesetih godina.
U osnovnom tehničkom smislu, ASCII standard podrazumeva da se brojevi u rasponu od 0 do 127 koriste za kodiranje velikih i malih slova engleskog alfabeta, cifara i drugih pažljivo biranih specijalnih znakova.
ASCII tabela
ASCII tabela sadrži 128 znakova (koje možete videti u donjem formularu) i formatirana je tako da prve 32 pozicije zauzimaju 'kontrolni znakovi' (to jest, znakovi koji se ne vide u štampi ili na ekranu, već imaju posebnu funkciju u različitim računarskim sistemima), dok ostale pozicije zauzimaju znakovi koji se mogu videti i predstavljeni su grafičkim simbolima.
Što se tiče kontrolnih znakova koji zauzimaju početne pozicije (i ne vide se u ispisu), izdvojićemo nekoliko uobičajenijih (sa dekadnim kodovima, za lakše snalaženje):
- 8 - Backspace (taster BACKSPACE)
- 13 - Carriage return (taster "ENTER")
- 27 - Escape (taster ESC)
- 32 - Space (razmak na tastaturi)
(Za ostale znakove koje prepoznajete od ranije, možete videti grafičke simbole.)
Pravilnosti u dodeljivanju mesta u tabeli ciframa i slovima
ASCII tabela može delovati donekle nasumično na prvi pogled, međutim, kada su u pitanju pozicije slova i cifara, zapravo postoji prilično jasna ideja vodilja: udesiti tabelu tako da se cifra 1, kao i veliko i malo slovo 'A' - "poklope sa jedinicom".
Da pojasnimo o čemu se radi ....
Kao prvo, jasno je da poziciju #1 u ASCII tabeli (niti bilo koju drugu poziciju), ne mogu zauzimati tri različita znaka istovremeno, vidimo i to, da nijedan od tri navedena znaka nije na prvoj poziciji u tabeli, i takođe - ne deluje na prvi pogled da znakovi '0', 'A' i 'a' imaju previše veze sa kodnim brojevima 48, 65 i 97 (sa kojima su povezani).
Međutim, ako se pažljivije sagledaju binarne vrednosti koje su pripisane ciframa i slovima, može se zapaziti red i pravilnosti.
Ako posmatramo zapis cifara preko ASCII tabele (uzećemo samo nekoliko prvih cifara za primer) ....

.... može se zapaziti da bitovi na najnižim pozicijama odgovaraju vrednostima cifara (uz uključene bitove na pozicijama 32 i 16, što daje zbir 48):
- cifra
'0'
ima ASCII kod48
, što se može shvatiti kao48 + 0
- cifra
'1'
ima ASCII kod49
, što se može shvatiti kao48 + 1
- cifra
'2'
ima ASCII kod50
, što se može shvatiti kao48 + 2
Ista ideja stoji i iza zapisa velikih slova:

.... gde se takođe može zapaziti da bitovi na najnižim pozicijama odgovaraju pozicijama znakova u engleskom alfabetu ....
- znak
'A'
ima ASCII kod65
, što se može shvatiti kao64 + 1
- znak
'B'
ima ASCII kod66
, što se može shvatiti kao64 + 2
- znak
'C'
ima ASCII kod67
, što se može shvatiti kao64 + 3
Mala slova takođe se kodiraju po sličnom principu:

Bitovi na najnižim pozicijama (ponovo) odgovaraju pozicijama znakova u engleskom alfabetu i takođe, kodne vrednosti su veće za 32 u odnosu na velika slova:
- znak
'a'
ima ASCII kod97
, što se može shvatiti kao64 + 32 + 1
- znak
'b'
ima ASCII kod98
, što se može shvatiti kao64 + 32 + 2
- znak
'c'
ima ASCII kod99
, što se može shvatiti kao64 + 32 + 3
Poznavanje navedenih pravilnosti dobro dođe u pisanju programa.
Manipulacija bitovima i drugi programski kodovi za ispis znakova
Manipulacijom bitova (pod uslovom da smo sigurni da znak jeste - "ono što bi trebalo da bude"), vrlo lako možemo pretvarati velika slova u mala, mala slova u velika i takođe, od znakova koji predstavljaju cifre, lako možemo dobiti odgovarajuće celobrojne vrednosti.
Na primer:
malo_slovo = veliko_slovo | 32; // 'A' => 'a'
veliko_slovo = malo_slovo & ~32; // 'a' => 'A'
cifra = znak_za_cifru & 15; // '1' => 1
Kad smo već kod programskih kodova, u C-u je veoma lako dobiti ispis kodne vrednosti znaka:
char c = 'A';
printf("%c\n", c); // A
printf("%d\n", c); // 65
Pomenimo da se navedene operacije mogu izvesti i preko JavaScript-a * (pri čemu ima i dodatnih mogućnosti):
let c = 65;
console.log(`c;`); // A
console.log(c.toString(2)); // 01000001
console.log(c.toString(16)); // 41
console.log(c); // 65
Naravno, većina popularnih jezika takođe omogućava slične zahvate.
Proširene ASCII tabele / kodne stranice
ASCII tabela je sedmobitna (nasleđe iz starijih vremena), ali se praktično zapisuje preko osam bita (1 bajt), pa je jasno da u takvom zapisu postoji mesta za dodatnih 128 znakova.
U periodu posle početnog 'uhodavanja' sa ASCII standardom, a pogotovo između kraja sedamdesetih i sredine devedesetih godina dvadesetog veka, pojavile su se brojne 'varijacije na temu' ASCII tabele.
U tom smislu, termin 'kodna stranica' (eng. code page), odnosi se na konkretnu 'proširenu ASCII tabelu' (koja se koristi u određenom računarskom sistemu).
U okviru različitih kodnih stranica, pozicije iznad 127 korišćene su za dodavanje podrške za specijalne znakove opšteg tipa, ili, za definisanje znakova različitih svetskih pisama.
Kodna stranica 437
Uzmimo kao prvi primer (iz razloga nekadašnje velike popularnosti), kodnu stranicu 437, izmenjenu i proširenu ASCII tabelu koja je uvedena sa prvobitnim PC računarima, odnosno sa operativnim sistemom DOS (a korišćena je i u ranim verzijama Windows-a):
199 - ╟
200 - ╚
201 - ╔
195 - ├
192 - └
218 - ┌
30 - ▲
31 - ▼
Kodna stranica 437 nije smišljena sa ciljem da pruži podršku za znakove iz "ne-anglosaksonskih" pisama, već da omogući prikaz dodatnih specijalnih znakova, pre svega onih koji su korišćeni za iscrtavanje prozora u okviru tekstualnog okruženja (verujemo da se programeri sa podužim stažom i dalje sećaju vremena kada su se znakovi sa gornje slike koristili za kreiranje prozora u DOS programima). *
Međutim, u poslednjim godinama pre nego što je standard UNICODE (praktično) istisnuo kodne stranice iz upotrebe, pojam 'kodna stranica' se tipično vezivao za proširene ASCII tabele sa podrškom za različita pisma, pa ćemo stoga, pre nego što konačno pređemo na UNICODE i formate za kodiranje znakova (pre svega UTF-8, ali i druge), napraviti kratak pregled nekada popularnih kodnih stranica sa podrškom za ćirilične i latinične znakove. **
Kodna stranica 1251 (ćirilica)
Kodna stranica 1251, odgovara znakovima srpskog ćiriličnog pisma (zarad preglednosti, navešćemo samo nekoliko znakova) ....
230 - ж
142 - ћ
247 - ч
248 - ш
Kodna stranica 852 (latinica)
Za latinična pisma iz slovenskih jezika, bila je zadužena kodna stranica 852:
159 - č
158 - ć
231 - š
190 - ž
Kodna stranica 737 (grčki alfabet)
Da bismo dodatno ilustrovali (relativnu) * proizvoljnost razmeštaja znakova u kodnim stranicama, uzećemo za primer još i Kodnu Stranicu 737, preko koje su definisana grčka slova (nekoliko pozicija koje smo već navodili) ....
158 - η
159 - θ
231 - ύ
240 - Ώ
Dovoljno je pogledati prethodne primere i doći do zaključka da kodne stranice (bar iz današnje perspektive) deluju pomalo neorganizovano, kao i to da bilo kakva greška koja bi dovela do pogrešnog odabira kodne stranice, dovodi do toga da tekst postane praktično nečitljiv (greške su svakako manje izražene u slučajevima kada se pomešaju različite verzije latiničnih pisama, dok, ako pomešamo, na primer, ćirilicu i grčko pismo - nećemo biti "takve sreće").
Naravno, može se iz sadašnje perspektive gledati na kodne stranice na način kako pojedini ljudi posmatraju fosilne ostatke predaka homo sapiensa, ali, evolucija se ne dešava 'preko noći', nekada nije bilo 'mostova i puteva', i trebalo je ipak vremena i truda da se sve izgradi kako dolikuje.
U svakom slučaju, do sada opisana situacija se može rezimirati na sledeći način:
- ASCII tabela nudi razuman raspored znakova i podršku za engleski alfabet, ali - ne podržava znakove ostalih pisama, niti druge (specijalne) znake
- kodne stranice su (u praktičnom smislu), suviše neorganizovane
.... i iz svega se jasno može zaključiti da je bilo potrebno iznaći praktičnije rešenje.
UNICODE
UNICODE je standard za kodiranje znakova iza koga stoji nekoliko osnovnih ideja:
- glavni cilj je - obuhvatiti sve znakove, iz svih svetskih pisama, odnosno - koliko god je moguće znakova
- svaki znak mora imati jedinstvenu kodnu poziciju (eng. code point) - što je pristup sličan ASCII standardu
- UNICODE specifikacija mora biti potpuno kompatibilna sa ASCII specifikacijom (kodovi znakova iz ASCII tabele odgovaraju kodnim pozicijama iz UNICODE specifikacije)
UNICODE konzorcijum osnovan je krajem osamdesetih godina dvadesetog veka, osnivači projekta su programeri Džo Beker (Joe Becker), Li kolins (Lee Collins) i Mark Dejvis (Mark Davis), a podršku pružaju brojni saradnici iz različitih računarskih kompanija (pogotovo onih koje su direktno zainteresovane za održavanje standarda u oblasti obrade teksta na računarima).
U početku, specifikacija je obuhvatala nekoliko desetina hiljada znakova (što svakako nije mali broj), ali, već do početka 21. veka, prevaziđen je broj od 100 hiljada znakova, što je zahtevalo 'prepravljanje' tehničkih rešenja za enkodiranje znakova.
U kontekstu tehničkih rešenja, od svih odlika UNICODE-a koje smo naveli, posebno se ističe očuvanje kompatibilnosti sa ASCII standardom (pri čemu je doneta racionalna i praktična odluka da se ASCII tabela "ne dira" - već samo proširi), što posebno dolazi do izražaja ako se za enkodiranje UNICODE znakova koristi format UTF-8 (o svemu navedenom ćemo pisati detaljnije u nastavku).
Enkodiranje UNICODE znakova
Znakovi se u okviru UNICODE standarda kodiraju prema dogovoru, tako da svakom znaku odgovara jedinstvena 'kodna pozicija' (eng. code point), čime je otklonjena zbrka koju je (ranije) unosilo korišćenje različitih kodnih stranica:
- prvih 128 pozicija zauzimaju redom znakovi iz ASCII tabele
- zatim slede uobičajenija pisma evropskih jezika, sa znakovima koji zauzimaju kodne pozicije relativno blizu početnih ASCII pozicija (na primer, ćirilica, latinica, grčki i ruski alfabet i sl)
- više pozicije zauzimaju manje uobičajena (ali i dalje široko rasprostranjena), pisma sa velikim brojem znakova (kinesko, japansko, korejsko, indijsko i sl)
- najviše / najudaljenije pozicije, zauzimaju najrazličitije vrste specijalnih znakova (emotikoni i sl)
Sledeće pitanje je: kako (zapravo) kodirati kodne pozicije?
ASCII znakovi zauzimaju 1 bajt (zapravo 7 bita; manje od jednog celog bajta), dok sa UNICODE znakovima, kojih ima mnogo više od 27, to nije i ne može biti slučaj.
Na primer, sledeći znakovi (uzmimo za primer nekoliko znakova različite "udaljenosti" od ASCII pozicija) ....
Č - 268
∑ - 8721
✅ - 9989
🙂 - 128578
.... shodno svojim kodnim pozicijama, očigledno zahtevaju više od jednog bajta za zapis (dva, tri, ili četiri).
Kao prvobitno rešenje, pojavio se standard UCS-2 (Universal Coded Character Set), koji je podrazumevao kodiranje svakog znaka preko dva bajta.
U poslednjoj deceniji 20. veka, u vreme dok je ukupan broj kodnih pozicija bio manji od 216 (65536), format UCS-2 bio je sasvim adekvatan, ali, nadomak 21. veka - broj kodnih pozicija počeo je raste ....
UNICODE Transformation Format (UTF-16 - UTF-32 - UTF-8)
Kada je broj kodnih pozicija prevazišao smeštajni kapacitet 16-bitnih promenljivih, uveden je standard UTF-16, koji predstavlja proširenje specifikacije UCS-2, a potom su uvedeni i standardi UTF-32 i UTF-8.
U nastavku ćemo posvetiti pažnju tehničkim detaljima navedenih metoda za kodiranje znakova, prednostima i nedostacima različitih metoda, kao i oblastima primene.
UTF-16
Format UTF-16 (kao jedan od mogućih formata za kodiranje UNICODE znakova), predstavlja unapređenje formata UCS-2, * uz očuvanje osnovne ideje da se za zapis znakova koriste blokovi od 16 bita.
Početna pretpostavka da će (za)uvek biti dovoljno koristiti 216 znakova za zapis svih pojedinačnih UNICODE znakova je, kao što smo već nagovestili, odavno opovrgnuta i takođe, jasno je da se preko jednog bloka od 16 bita ne mogu kodirati brojevi veći od 216, pa je stoga jedino rešenje - uvođenje dodatnog 16-bitnog bloka (po potrebi), uz korišćenje posebnog (i 'ne baš skroz jednostavnog') postupka, na koji ćemo se osvrnuti u nastavku.
Krenimo redom i razmotrimo prvo situacije u kojima nije potrebno uvođenje dodatnih bajtova ....
Kada su u pitanju niže pozicije, zapis znaka preko formata UTF-16 (baš kao i u slučaju starog standarda UCS-2), obavlja se uz korišćenje dva bajta.
Primera radi, znak 'Č' (koji smo već pominjali) - čija je kodna pozicija 268 - može se kodirati na sledeći način:

Za znak kao što je 'Č' (sa kodnom pozicijom koja je veća od 127 i manja od 65536), korišćenje dva bajta je racionalan i optimalan izbor.
Ako format UTF-16 primenimo za kodiranje znaka 'A', čija je kodna pozicija ("i dalje") 65 ....

.... vidimo da je zapis (naravno) moguć, ali da pri tom dolazi do (reklo bi se - nepotrebnog), dupliranja memorijskog zauzeća:

Iako UTF-16 dozvoljava korišćenje dodatne grupe od dva bajta (po potrebi), nije moguće selektivno koristiti samo jedan bajt za znakove sa prvih 128 pozicija (koje, same po sebi, ne zahtevaju dodatne bitove).
Pošto smo se osvrnuli na znakove koji se mogu kodirati preko jednog 16-bitnog bloka, vraćamo se na diskusiju o kodnim pozicijama sa kojima to nije slučaj ....
Zapis kodnih pozicija većih od 65535 - parovi surogata
U okviru UNICODE specifikacije, postoji svojevrsna "rupa" ('bermudski trougao'), između kodnih vrednosti 55296 (0xD800) i 57343 (0xDFFF).
Navedeni raspon ne koristi se za (direktno) kodiranje znakova, već pojava kodne vrednosti iz navedenog raspona (pri čitanju teksta), sugeriše da je u pitanju znak sa kodnom pozicijom koja je veća ili jednaka 65536, i takav znak se tumači na poseban način.
Sam raspon kodnih vrednosti od 55296 do 57343, podeljen je na dve grupe tzv. "surogata":
- pozicije od 55296 do 56319 zauzimaju viši surogati (eng. high surrogates)
- pozicije od 56320 do 57343 zauzimaju niži surogati (eng. low surrogates)

Oba raspona sadrže po 1024 vrednosti (1010), a prostim nadovezivanjem * dve desetobitne vrednosti, može se dobiti jedna dvadesetobitna vrednost, što omogućava kodiranje znakova sve do kodne pozicije 1048576 (1020 = 1048576).
Kao što smo ranije nagovestili, ceo postupak (naravno), podrazumeva korišćenje dva uzastopna 16-bitna bloka.
Kada program koji obavlja dekodiranje po standardu UTF-16 naiđe na 16-bitni blok iz raspona viših surogata (55296-56319), pojava takvog bloka se tumači kao informacija, koja se tiče toga da je za pravilno dekodiranje znaka potrebno pročitati još jedan 16-bitni blok (za koji se očekuje da mora pripadati rasponu nižih surogata, 56320 - 57343) *
Kodna pozicija se formira spajanjem dve grupe od deset bitova, koje se 'izvlače' iz dve uzastopne 16-bitne vrednosti iz raspona surogata.
Prvo ćemo prikazati primer kodiranja znaka ("da bismo uopšte imali šta da čitamo"), a konkretna kodna pozicija koju ćemo zapisati po formatu UTF-16, biće 128578 - osnovni "smajli".
Za početak, potrebno je oduzeti vrednost 65536 (216), od broja koji predstavlja kodnu poziciju znaka,:

Posle oduzimanja, 20-bitni zapis dobijene vrednosti (63042), deli se na dva 10-bitna bloka (kao što smo već nagovestili), posle čega se viših 10 bitova 'utiskuje' u raspon viših surogata, dok se preostalih 10 bitova 'utiskuje' u raspon nižih surogata.

Da bi 10 viših ('levih') bitova, bilo 'utisnuto' u raspon viših surogata, potrebno je sabrati prikazanu desetobitnu vrednost (111101), sa početnom pozicijom raspona viših surogata (55296, odnosno 0xD800):

Nakon sabiranja, dobijeni rezultat (55357) - zapisuje se u prvi 16-bitni blok:

Nižih ('desnih') 10 bitova (1001000010), sabira se sa početnom pozicijom raspona nižih surogata (563200, odnosno 0xDC00):

.... a dobijeni rezultat (56898), zapisuje se kao druga kombinacija od dva bajta:

Znak je sada kodiran.
Pri čitanju, potrebno je proći kroz sledeće korake (smatramo da je uspešno prepoznato da dve uzastopne 16-bitne vrednosti pripadaju odgovarajućim rasponima surogata i prolazi se kroz 'obrnuti postupak'):
- od prve 16-bitne vrednosti oduzima se 0xD800 (u binarnom zapisu, praktično se isključuju svi bitovi, osim onih na 10 najnižih pozicija) *
- od druge 16-bitne vrednosti oduzima se 0xDC00 (i u ovom slučaju praktično ostaju samo bitovi na 10 najnižih pozicija) *
- bitovi u prvoj grupi pomeraju se za deset mesta ulevo i spajaju se sa bitovima iz druge grupe, preko pomoćne 32-bitne promenljive, a potom se rezultat uvećava za 65536 **
nizi = nizi & 1023; // nizi & 0000001111111111
visi = visi & 1023; // visi & 0000001111111111
int cp = (visi << 10) | nizi + 65536;
Za razliku od gornjeg primera, u kome smo samo pretpostavili da je redosled 16-bitnih blokova ispravan, u praksi se podaci moraju proveravati.
Provera se obavlja na sledeći način:
- 16-bitni blok koji ne pripada rasponu surogata, tumači se kao zaseban znak
- pojava 16-bitnog bloka iz raspona viših surogata, podrazumeva čitanje sledećeg bloka (za koji se, kao što smo već naveli ranije, očekuje da pripada rasponu nižih surogata)
- situacija u kojoj se 16-bitni blok sa vrednošću iz raspona nižih surogata pojavljuje pre odgovarajuće vrednosti iz raspona viših surogata - predstavlja signal da je došlo do greške u čitanju
- situacija u kojoj se 16-bitni blok sa vrednošću iz raspona viših surogata pojavljuje posle 16-bitnog bloka koji takođe sadrži vrednost iz raspona viših surogata, predstavlja signal da je došlo do greške
- situacija u kojoj se vrednost iz raspona viših surogata, ili vrednost iz raspona nižih surogata, pojavljuje samostalno - takođe predstavlja signal da je došlo do greške
Prednosti, nedostaci, oblast upotrebe
Da bi UTF-16 (kao način za kodiranje znakova), bio zaista univerzalan, morala bi biti otklonjena dva nedostatka:
- zapis UTF-16 očigledno nije direktno kompatibilan sa ASCII datotekama (direktno čitanje ASCII datoteke, pri čemu bi se znakovi koji inače zauzimaju jedan bajt, čitali u blokovima od po dva bajta, dovelo bi do grešaka)
- konvertovanje ASCII datoteka u UTF-16 datoteke, duplira memorijsko zauzeće.
Pomenuti nedostaci čine format UTF-16 nepraktičnim za kodiranje web stranica i trajno čuvanje teksta, ali se zato UTF-16 i dalje koristi kao interni format za zapis znakova u operativnim sistemima i programskim jezicima (što su situacije u kojima dodatno memorijsko zauzeće ne predstavlja preveliki problem, pa stoga UTF-16 predstavlja praktičnije rešenje od UTF-8 zapisa i starijih, prevaziđenih rešenja).
UTF-32
Format za kodiranje UTF-32, predstavlja način da se bilo koji znak zapiše neposredno, preko 32 bita - bez šifriranja / utiskivanja bitova (nasuprot formatu UTF-16, ili formatu UTF-8, o kome ćemo tek pisati).
Međutim, iako 32-bitni format (232 = 4294967296), nedvosmisleno omogućava da se obuhvati celokupan raspon UNICODE znakova, zapis sam po sebi nije optimalan i memorijsko zauzeće je - u praktičnom smislu - preveliko.
Ako kao prvi primer ponovo uzmemo znak '🙂' (popularni smajli), čija kodna pozicija 128578 "prirodno" zahteva 3 bajta za zapis ....

.... jasno je da se i u ovakvom, najmanje "dramatičnom" slučaju, jedan ceo bajt uopšte ne koristi:

Po pitanju "rasipanja prostora", stvari su još ozbiljnije kada zapisujemo uobičajenije UNICODE znakove:

.... u kom slučaju se nepotrebno koriste dva bajta ....

..... a naravno, najviše 'rasipanja' ima u situaciji kada se zapisuju znakovi iz ASCII specifikacije ....

.... u kom slučaju se nepotrebno koriste tri bajta:

Format UTF-32 (za razliku od formata UTF-16 i UTF-8), nije previše zastupljen u praksi, ali, povremeno se koristi u određenim programima.
Kao što smo već ranije spominjali, za prenos i čuvanje većih količina teksta, najčešće se koristi prilično elegantno rešenje u vidu formata UTF-8 ....
Format UTF-8 (specifikacija)
Osnovna ideja koja stoji iza formata UTF-8, podrazumeva kodiranje znakova preko blokova od 8 bita, tako da se znakovi iz ASCII tabele (i dalje) zapisuju preko jednog bajta, a da se za ostale znakove uvode dodatni bajtovi, ali - samo po potrebi.
Format UTF-8 omogućava prostornu uštedu, ali, zapis viših UNICODE pozicija (slično kao kada se koristi UTF-16), relativno je kompleksan i zahteva malo više procesorskog vremena pri čitanju i pisanju znakova (u odnosu na prosto čitanje ASCII znakova).
Sve što smo naveli "lepo zvuči", ali, glavno pitanje je - kako se ideje sprovode u delo.
Primeri formiranja kodnih pozicija preko jednog ili dva bajta
Znakovi iz ASCII tabele ....

.... zapisuju se na isti način kako se zapisuju u samoj ASCII tabeli.
Međutim (kao što verovatno naslućujete), glavna informacija u smislu enkodiranja/dekodiranja znaka je - prvi bit:

- ako je prvi bit 0 - u pitanju je (praktično) znak iz ASCII tabele
- ako je prvi bit 1 - u pitanju je znak sa kodnom pozicijom preko 127, koji se kodira uz korišćenje dva ili više bajta
Na gornjoj slici, prvi bit nije uključen i upravo je to informacija UTF-8 parseru da za dekodiranje datog znaka neće biti potrebno čitanje dodatnih bajtova (to jest, da je znak pročitan).
U tome leži ranije pomenuta elegancija UTF-8 pristupa: ASCII je 7-bitni standard, ali (kao što znamo), tipično se prenosi preko osam bita, što ostavlja mogućnost da se prvi bit koristi kao prenosilac informacije o tome da li će za dekodiranje određene kodne pozicije biti dovoljno da se pročita jedan bajt (kao u prethodnom slučaju), ili će biti potrebni i dodatni bajtovi.
Ako je (nasuprot prethodnom slučaju), prvi bit uključen, potrebno je očitati bar dva bajta:

Očitavanje znakova funkcioniše prema sledećim opštim pravilima:
- ukoliko je prvi bit u prvom bajtu 0, kodna pozicija znaka se čita iz prvog bajta (iz preostalih 7 bita)
- ukoliko je prvi bit u prvom bajtu 1, čitaju se i naredni bitovi, sve dok se ne očita 0
- broj uzastopnih bitova sa vrednošću 1, na početku prvog bajta, praktično definiše ukupan broj bajtova koji se koriste za kodiranje znaka
- prva dva bita u preostalim bajtovima moraju biti redom
1
i0
- preko svih preostalih bitova (u svim bajtovima), koji se posmatraju kao jedinstvena niska bitova, * zapisuje se kodna pozicija
* Pokazaćemo preko primera sa slikama kako dekodiranje funkcioniše u praksi (vraćamo se na dekodiranje znaka 'Č').
Kada se na prvoj poziciji (u prvom bajtu), pojavi jedinica, potrebno je očitati i drugi bit, i - ako se na drugoj poziciji takođe pojavi jedinica a na trećoj poziciji nula:

.... to znači da je za formiranje kodne pozicije potrebno pročitati još jedan bajt (ili malo drugačije, samo još jedan bajt, odnosno, da se za dati znak koriste ukupno dva bajta):
Drugi bajt mora počinjati kombinacijom bitova 10
....

.... a kada se izdvoje preostali bitovi u oba bajta ....

.... dobija se 11 bitova preko kojih je moguće zapisivati znakove sa kodnim pozicijama u rasponu od 128 do 2047 (211 - 1).
Konkretna kombinacija bitova 100001100
, definiše vrednost 268 (256 + 8 + 4), što je kodna pozicija znaka 'Č' sa kojom smo se već susretali.
Svrha kontrolnih bitova
Kodiranje drugog bajta (odnosno, ostalih bajtova, u opštem slučaju), kombinacijom bitova 10
na samom početku, deluje na prvi pogled pomalo "nepotrebno", ali, u pitanju je kontrolni mehanizam koji je uveden (pre svega) zarad situacija u kojima se tekst ne prenosi u obliku datoteka, već znak-po-znak (streaming), i takav kontrolni mehanizam pruža informaciju o potencijalnim greškama koje mogu nastati u prenosu.
Za početak, uzmimo kao primer situaciju u kojoj se očitava znak koji je kodiran preko dva bajta (pri čemu se na prvoj poziciji uredno pojavio bajt koji počinje sa 110
):
- ako drugi bajt počinje nulom, to je signal da znak nije pravilno enkodiran, što znači da prethodni bajt treba odbaciti (i formirati kodnu vrednost korišćenjem samo drugog bajta, koji se u tom slučaju prepoznaje kao samostalni "ASCII" znak)
- ako drugi bajt počinje sa
11
, to je takođe signal da prethodni bajt treba odbaciti, ali, u navedenom slučaju se drugi bajt, koji počinje sa11
, tretira kao prvi bajt u višebajtnom kodiranju UNICODE znaka *
Prethodno navedenim pravilima - koja važe i u situacijama kada se određeni znak kodira preko tri ili više bajta - dodaćemo još i sledeća pravila:
- ukoliko se, pri čitanju znaka, bajt koji počinje sa
10
pojavi pre bajta koji počinje sa110
(ili1110
i sl) - u pitanju je greška. - ukoliko je znak kodiran preko više od dva bajta (tri, četiri i sl), i pri tom bilo koji od bajtova posle prvog, ne počinje sa
10
- takođe je u pitanju greška **
Primer formiranja kodne pozicije preko tri ili više bajtova
Za formiranje kodnih pozicija koje prevazilaze vrednost 2047, koristi se (bar) tri bajta, po prethodno opisanom principu: u prvom bajtu je zapisano koliko bajtova se koristi za enkodiranje/dekodiranje znaka, a ostali počinju sa 10
.

Za enkodiranje znaka preko 3 bajta, raspoloživo je 16 bitova
, i stoga je maksimalna kodna pozicija koja se može zapisati preko 3 bajta: 65535
.
Za formiranje kodnih pozicija koje prevazilaze 65535, potrebno je koristiti (bar) četiri bajta.

U situaciji kada se koristi 4 bajta za enkodiranje znaka, na raspolaganju je 21 bit
, i stoga je maksimalna kodna pozicija koja se može zapisati preko četiri bajta: 2097151
.
Teoretski, moguće je koristiti i pet, pa čak i šest bajtova (što možete isprobati u mini-aplikaciji na kraju ovog članka), ali, budući da se preko četiri bajta može zapisati bilo koji znak čija je kodna pozicija između 65536 i 2097151, pri čemu navedena gornja granica osetno premašuje broj znakova koji su trenutno prisutni u UNICODE specifikaciji, formatima koji koriste više od četiri bajta - ne moramo pridavati preveliki značaj (u praktičnom smislu).
Primer čitanja poruke koja je enkodirana po formatu UTF-8
Da bismo još bolje utvrdili princip kodiranja znakova preko formata UTF-8, razmotrićemo primer.
Tekstualna datoteka sastoji se iz sledećih bajtova: 196 140 97 197 161 97
:

.... i potrebno je pročitati poruku koja je u datoteci sadržana.
Početak prvog bajta ....

.... sugeriše da je za 'dešifrovanje' znaka potrebno pročitati još jedan bajt:

Preko 11 bitova, definisana je vrednost 268 - što predstavlja znak 'Č':

Sledeći bajt, počinje nulom ....

.... što znači da se odmah može pročitati preostalih 7 bitova, koji daju vrednost 97, što predstavlja znak 'a':

Početak četvrtog bajta ....

.... (slično kao i početak prvog bajta), sugeriše da je za dekodiranje znaka potrebno pročitati ukupno dva bajta (odnosno - još jedan bajt) ....

.... a preko datih 11 bitova, definisana je vrednost 353, odnosno znak 'š':

Šesti (poslednji) bajt počinje nulom ....

.... što znači da se znak odmah može dekodirati preko preostalih 7 bitova (ponovo je u pitanju vrednost 97, to jest, znak 'a'):

Poruka sadržana u tekstualnoj datoteci je: "Čaša".

Formulari: UTF-8 enkoder / dekoder
Kao što smo ranije nagovestili, sledeće formulare možete iskoristiti za isprobavanje 'teorije' sa kojom smo se upoznali kroz članak.
Enkodiranje
Enkoder uzima vrednost iz numeričkog ulaznog polja, vraća detaljan prikaz strukture bitova enkodiranog znaka, brojčane vrednosti bajtova (u dekadnom sistemu) i ispisuje znak.
Dekodiranje
Dekoder iz ulaznog polja uzima niz dekadnih brojeva u rasponu od 0 do 255 (koji su razdvojeni razmacima i praktično predstavljaju "bajtove"), a ukoliko niz "bajtova" predstavlja poruku koja je pravilno kodirana po standardu UTF-8 - dekodirana poruka će biti ispisana ispod ulaznog polja.
Zaključak / kratka analiza
Iako smo se u članku "podosta napričali" o metodama za enkodiranje/dekodiranje znakova, tema za razgovor ima još ....
Najviše prostora posvetili smo standardu UTF-8 i implicirali da navedeni standard (uglavnom) predstavlja najoptimalnije rešenje, međutim, osvrnućemo se i na prilično očigledne nedostatke UTF-8 zapisa:
- za predstavljanje znakova pojedinih pisama preko UTF-16 enkodiranja (primer: kinesko pismo), dovoljna su 2 bajta, dok je, kada se za iste znakove koristi UTF-8 enkodiranje, potrebno koristiti 3 bajta
- procedure enkodiranja i dekodiranja UTF-8 znakova su relativno komplikovane za implementaciju, pa je stoga i enkodiranje / dekodiranje UTF-8 znakova nešto sporije
Ako izraz "win-win" (iz engleskog jezika), shvatimo kao izraz koji označava pogodbe u kojima svaka strana dobija, ili situacije u kojima sve izlazi na dobro, onda bismo situaciju sa metodama UTF enkodiranja mogli (pomalo slikovito), shvatiti kao svojevrsnu "lose-lose" situaciju (jer nijedna od opcija nije optimalna sama po sebi).
Sa druge strane, nedostaci ni izdaleka nisu "zabrinjavajući", i stoga je potrebno biti praktičan:
- tamo gde je optimalno koristiti UTF-16 - koristi(će) se UTF-16 (isto važi i za UTF-32, format koji je, kao što smo već naveli, ipak ređe u upotrebi)
- programski jezici godinama unazad sadrže funkcije za enkodiranje/dekodiranje po standardima UTF-8, UTF-16 i UTF-32, tako da programeri koji ne žele time da se bave - ne moraju time da se bave
- savremeni računari su duži niz godina dovoljno brzi da omoguće da razlika u brzini između čitanja ASCII datoteka (sa jedne strane), i UTF-8 datoteka (sa druge) - bude praktično zanemarljiva
Takođe, mogli bismo spomenuti da metode enkodiranja UTF-16, UTF-32 i UTF-8 nisu jedine postojeće (ali, svakako jesu najuobičajenije, pogotovo UTF-8), pa, ako vas tematika predstavljanja znakova na računarima dodatno zanima - alternativne metode za kodiranje znakova mogu biti dobra ideja za dalje istraživanje ....