Codifica binaria di caratteri

Un computer sa trattare soltanto numeri, e più precisamente sequenze di bit. Per memorizzare un testo, quindi, bisogna trasformare ogni carattere in un numero: è ciò che si chiama codifica dei caratteri.

Come per i numeri interi (complemento a 2) e per i numeri reali (virgola mobile), anche qui vale lo stesso principio: i bit non hanno un significato di per sé. La sequenza 01000001 può essere il numero intero 65, oppure la lettera A: dipende solo dalla convenzione con cui la si interpreta. Una codifica dei caratteri è proprio una convenzione di questo tipo, cioè una tabella condivisa che associa a ogni carattere un numero.

Codifica ASCII

ASCII (American Standard Code for Information Interchange) è una delle prime codifiche di larga diffusione, nata negli anni Sessanta. Usa 7 bit per carattere, quindi può rappresentare 2⁷ = 128 simboli, numerati da 0 a 127. In memoria ogni carattere occupa un byte (8 bit), con il bit più significativo posto a 0.

I 128 codici si dividono in due gruppi:

Un esempio: la parola “Ciao”

Carattere Codice decimale Codice esadecimale Codice binario (8 bit)
C 67 43 01000011
i 105 69 01101001
a 97 61 01100001
o 111 6F 01101111

Nella memoria di un computer la parola “Ciao” è quindi la sequenza di byte 43 69 61 6F.

Una tabella ben organizzata

I codici non sono assegnati a caso. Osservando la tabella si scoprono delle regolarità molto utili:

Gruppo Primo e ultimo carattere Codici (decimale) Codici (esadecimale)
Cifre 0 … 9 48 … 57 30 … 39
Lettere maiuscole A … Z 65 … 90 41 … 5A
Lettere minuscole a … z 97 … 122 61 … 7A

Da qui discendono alcune proprietà:

Il limite dell’ASCII

L’ASCII è nato per l’inglese, e non contiene le lettere accentate (à, è, é, ì, ò, ù), né i caratteri di altri alfabeti (greco, cirillico, arabo…) o di scritture come il cinese e il giapponese. Con soli 128 codici non c’è spazio per tutto questo, e serviva una soluzione più ampia.

Qui puoi trovare la tabella ASCII completa.

Unicode

Prima di Unicode: le estensioni a 8 bit

Usando anche l’ottavo bit di ogni byte, i simboli disponibili diventano 256: i primi 128 restano quelli dell’ASCII, mentre gli altri 128 possono essere usati per lettere accentate e altri caratteri. Ne nacquero molte versioni, ciascuna pensata per un gruppo di lingue (per l’Europa occidentale, per esempio, ISO 8859-1 e Windows-1252).

Il problema è che le versioni erano incompatibili tra loro: lo stesso byte poteva significare lettere diverse a seconda della tabella usata. Un testo aperto con la tabella sbagliata mostra simboli senza senso. Un caso tipico che si incontra ancora oggi è “perché” che diventa “perché”: è l’effetto di un testo scritto con una codifica e letto con un’altra.

Unicode: un unico catalogo di caratteri

Unicode è nato per risolvere il problema alla radice: un unico catalogo universale, che contiene i caratteri di tutte le lingue del mondo, i simboli matematici, le emoji e molto altro. Ogni carattere riceve un numero, detto code point, che si scrive in esadecimale preceduto da U+:

Carattere Code point
A U+0041
è U+00E8
€ U+20AC
😀 U+1F600

I code point vanno da U+0000 a U+10FFFF, quindi più di un milione di posizioni possibili. I primi 128 coincidono con l’ASCII: A è 65 (0x41) sia in ASCII sia in Unicode.

Unicode non è una codifica in bit. Il code point è un numero astratto: dice quale carattere è, ma non dice come scriverlo in memoria. Serve una seconda regola, ed è qui che entrano in gioco le codifiche, di cui la più diffusa è UTF-8.

Unicode e UTF-8: due livelli diversi

Unicode decide quale numero corrisponde a ogni carattere. UTF-8 decide quali byte usare per scrivere quel numero.

Un paragone con quanto già studiato può aiutare: il numero 232 è sempre lo stesso numero, ma può essere scritto in binario, in esadecimale (E8) e così via. Allo stesso modo il code point U+00E8 è sempre la lettera è, ma può essere scritto in byte in modi diversi. Le codifiche più usate sono UTF-8, UTF-16 e UTF-32:

Codifica Byte per è (U+00E8) Byte per € (U+20AC)
UTF-8 C3 A8 E2 82 AC
UTF-16 00 E8 20 AC
UTF-32 00 00 00 E8 00 00 20 AC
ISO 8859-1 E8 non rappresentabile

L’ultima riga mostra il limite delle vecchie codifiche: la ISO 8859-1 non ha spazio per il simbolo dell’euro. Unicode ha separato il catalogo (i code point) dalla codifica (i byte), e questo gli permette di avere più modi di scrivere gli stessi caratteri.

Come funziona UTF-8

UTF-8 è la codifica più usata al mondo (è quella della maggior parte delle pagine web). Ha lunghezza variabile: ogni carattere occupa da 1 a 4 byte, a seconda del suo code point. I caratteri più comuni occupano meno spazio.

Il primo byte dichiara quanti byte compongono il carattere, e i bit rimanenti contengono il code point. Lo schema è il seguente (le x sono i posti in cui vanno scritti i bit del code point):

Intervallo di code point Numero di byte Schema dei byte Bit disponibili per il code point
U+0000 – U+007F 1 0xxxxxxx 7
U+0080 – U+07FF 2 110xxxxx 10xxxxxx 11
U+0800 – U+FFFF 3 1110xxxx 10xxxxxx 10xxxxxx 16
U+10000 – U+10FFFF 4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 21

Esistono due tipi di byte:

Come si codifica un carattere

  1. Si scrive il code point in binario.
  2. Si guarda in quale riga della tabella ricade, per sapere quanti byte servono.
  3. Si aggiungono zeri a sinistra fino a riempire tutti i bit disponibili.
  4. Si distribuiscono i bit sulle x, da sinistra a destra, e si aggiungono i prefissi.

Esempio 1: A (U+0041), 1 byte. 0x41 in binario è 1000001, che sta in 7 bit. Con lo schema 0xxxxxxx si ottiene 01000001, cioè 41. È identico al codice ASCII.

Esempio 2: è (U+00E8), 2 byte. 0xE8 in binario è 11101000: non entra nei 7 bit di un solo byte, quindi servono 2 byte (11 bit disponibili). Si completa con zeri a sinistra: 00011101000, e si divide in due gruppi, di 5 e di 6 bit: 00011 e 101000.

Il risultato è C3 A8.

Esempio 3: € (U+20AC), 3 byte. 0x20AC in binario è 0010 0000 1010 1100 (16 bit). Si divide in gruppi di 4, 6 e 6 bit: 0010, 000010, 101100.

Il risultato è E2 82 AC.

Esempio 4: 😀 (U+1F600), 4 byte. 0x1F600 in binario è 11111011000000000 (17 bit). Si completa a 21 bit: 000011111011000000000, e si divide in gruppi di 3, 6, 6 e 6 bit: 000, 011111, 011000, 000000.

Il risultato è F0 9F 98 80.

Come si decodifica. Si legge il primo byte e se ne guarda il prefisso, che indica quanti byte compongono il carattere. Poi si tolgono i prefissi e si uniscono i bit rimasti, ottenendo il code point. Per esempio, E2 82 AC diventa 0010, 000010, 101100, cioè 0010000010101100 = 0x20AC, quindi €.

Perché UTF-8 si è affermato

Lo schema può sembrare complicato, ma ognuna delle sue caratteristiche ha una ragione precisa:

Un’ultima regola: ogni carattere va scritto con il minor numero di byte possibile. La lettera A potrebbe in teoria essere scritta in 2 byte (C1 81), ma è vietato: ammettere più modi di scrivere lo stesso carattere ha causato in passato vulnerabilità di sicurezza.

Da ricordare