BitLab vzorek, který se někdo zeptá

Tohle je statická kopie kapitoly pro vyhledávače. Interaktivní verze má animované obrázky, kontrolní otázky a tlačítka, která příklad načtou do kalkulačky.

Text: od ASCII k UTF-8

Písmeno není v paměti. V paměti je číslo, a někde je dohoda, které číslo znamená které písmeno. ASCII z roku 1963 má 128 znaků a vejde se do sedmi bitů — což stačilo pro angličtinu a pro nic jiného.

Osmý bit pak každý použil po svém: Latin-2, Windows-1250, KOI8, CP852 a další. Všechny se shodovaly na prvních 128 znacích a rozcházely na zbytku, takže české „ř“ bylo v každé jiné číslo. Odtud pochází „rozsypaný čaj“ — text se vykreslil podle jiné tabulky, než ve které vznikl.

Unicode odděluje dvě otázky, které se do té doby pletly

  • Které číslo patří kterému znaku? To je Unicode. „ř“ je U+0159, a bude jím navždycky. Čísel je přes milion (U+0000 až U+10FFFF).
  • Jak se to číslo uloží do bajtů? To je kódování — UTF-8, UTF-16, UTF-32 — a je jich víc, protože každé řeší jiný kompromis.

UTF-8 a proč vyhrálo

U+0159ř2 bajty110001010xC5úvodní: kolik bajtů jich je100110010x99pokračovací: vždycky 10…Modré bity jsou režie, zelené jsou samotný kód znaku.
Táhni kódem znaku a sleduj, kde přeskočí počet bajtů. Modré bity jsou režie kódování, zelené jsou samotný kód znaku. Zkus U+0041 (A), U+0159 (ř), U+4E2D (中), U+1F600 (😀).
rozsahbajtůtvar
U+0000 – U+007F10xxxxxxx
U+0080 – U+07FF2110xxxxx 10xxxxxx
U+0800 – U+FFFF31110xxxx 10xxxxxx 10xxxxxx
U+10000 – U+10FFFF411110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Tři vlastnosti, kvůli kterým to vyhrálo:

  1. ASCII je podmnožina. Anglický text je bajt po bajtu totožný s tím, co byl předtím. Žádná konverze, žádná migrace.
  2. Pokračovací bajt je poznat. Začíná vždycky 10, takže se dá skočit doprostřed souboru a najít hranici znaku couvnutím o pár bajtů. To u jiných kódování nejde.
  3. Nikde není nula. Žádný bajt uvnitř vícebajtového znaku není 0x00, takže céčkové řetězce zakončené nulou fungují dál.

Praktický důsledek pro češtinu: každé písmeno s diakritikou zabere dva bajty. Český text je tedy v UTF-8 asi o 5–10 % delší než anglický téže délky, a strlen() nad ním nevrací počet písmen. „Délka řetězce“ má nejmíň tři různé odpovědi — počet bajtů, počet kódů znaků a počet toho, co uživatel považuje za písmeno.

UTF-16 a ta věc s délkou dvě

UTF-16 ukládá většinu znaků do dvou bajtů, ale na ty nad U+FFFF nestačí — pro ně používá náhradní pár, dvě šestnáctibitová čísla z vyhrazeného rozsahu D800–DFFF. Tyhle poloviny nejsou znaky; existují jen jako útěková cesta a v UTF-8 nemají zápis.

Řetězec v JavaScriptu, v Javě i ve Windows API je UTF-16. Proto:

"😀".length === 2jeden znak, dvě jednotky UTF-16

To není chyba jazyka. Je to ta samá věc, jen viditelná: „délka“ znamená počet uložených jednotek, ne počet písmen.

Vzorce v této kapitole

UTF-8 — délka podle rozsahu
≤7F: 1 B · ≤7FF: 2 B · ≤FFFF: 3 B · výš: 4 B [B] RFC 3629