risorse | unicode
Il problema di rappresentare la scrittura nei calcolatori risale alle origini dell’informatica. La soluzione più frequentemente adottata è la definizione di una codifica di carattere che consiste nell’associare ad ogni simbolo dell’alfabeto in uso una particolare sequenza di bit. Questa associazione viene a volte erroneamente indicata col termine di set di caratteri, mappa dei caratteri, o code page.
Un tipico esempio di codifica, risalente al 1967 e ancora in uso in ambiente informatico, è lo standard ASCII – American Standard Code for Information Interchange.
La componente base di una codifica è il set di caratteri, ovvero l’insieme dei simboli che si intende considerare. Il set di caratteri della codifica ASCII comprende tutte le lettere dell’alfabeto inglese, maiuscole e minuscole, le dieci cifre decimali, i simboli di interpunzione più altri simboli grafici, oltre ad alcuni caratteri di controllo, come ad esempio l’a-capo. L’altro aspetto che caratterizza una codifica è l’unità di codifica, ovvero l’unità atomica utilizzata per costruire le rappresentazioni dei caratteri; nel caso dell’ASCII consiste di un pacchetto di 7 bit. Poiché l’ASCII attribuisce ad ogni carattere un’unica unità di codifica, e che ad ogni unità atomica è associato un carattere distinto, l’alfabeto ASCII è composto esattamente da 128 caratteri, con codici che vanno da 0000000 (0) fino a 1111111 (127).
Bit Mask | 6 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | |||
5 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | ||||
4 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | ||||
Col→ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | ||||
3 | 2 | 1 | 0 | Row↓ | ||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | NUL | DLE | SP | 0 | @ | P | ` | p |
0 | 0 | 0 | 1 | 1 | SOH | DC1 | ! | 1 | A | Q | a | q |
0 | 0 | 1 | 0 | 2 | STX | DC2 | " | 2 | B | R | b | r |
0 | 0 | 1 | 1 | 3 | ETX | DC3 | # | 3 | C | S | c | s |
0 | 1 | 0 | 0 | 4 | EOT | DC4 | $ | 4 | D | T | d | t |
0 | 1 | 0 | 1 | 5 | ENQ | NAK | % | 5 | E | U | e | u |
0 | 1 | 1 | 0 | 6 | ACK | SYN | & | 6 | F | V | f | v |
0 | 1 | 1 | 1 | 7 | BEL | ETB | ' | 7 | G | W | g | w |
1 | 0 | 0 | 0 | 8 | BS | CAN | ( | 8 | H | X | h | x |
1 | 0 | 0 | 1 | 9 | HT | EM | ) | 9 | I | Y | i | y |
1 | 0 | 1 | 0 | A | LF | SUB | * | : | J | Z | j | z |
1 | 0 | 1 | 1 | B | VT | ESC | + | ; | K | [ | k | { |
1 | 1 | 0 | 0 | C | FF | FS | , | < | L | \ | l | |
1 | 1 | 0 | 1 | D | CR | GS | - | = | M | ] | m | } |
1 | 1 | 1 | 0 | E | SO | RS | . | > | N | ^ | n | ~ |
1 | 1 | 1 | 1 | F | SI | US | / | ? | O | _ | o | DEL |
È immediato ricavare dalla tabella la configurazione di bit per la a maiuscola (carattere A):
A → col=4, row=1 ↓ ↓ 100 ↓ ↓ 0001 ↓ ↓ 100 0001 = 41 bit: 654 3210
Poiché i computer dell’epoca erano basati su architetture a 8 bit, la memorizzazione di un carattere ASCII in un byte lasciava un bit inutilizzato. Diversi costruttori e sviluppatori di software utilizzarono il bit extra nei modi più disparati, per esempio per indicare un'enfasi – corsivo, grassetto, sottolineatura… –, per indicare il carattere finale di una parola, o per estendere la codifica, introducendo simboli non presenti nella codifica ASCII, come ad esempio le lettere accentate o quelle dell’alfabeto greco o simboli grafici adatti a realizzare semplici sinottici.
La diffusione dei computer IBM-PC compatibili e la necessità di localizzare i software spinse verso la standardizzazione delle estensioni proprietarie del codice ASCII, introducendo il concetto di set di caratteri OEM – Original Equipment Manufacturer. La costante di questo tipo di codifiche è l’uso di unità di codifica di 8 bit: le prime 128 posizioni coincidono con quelle del codice ASCII, mentre le successive 128, dal codice 1000000 (128) al codice 11111111 (255), sono di volta in volta utilizzate per simboli diversi.
Una particolare mappatura prende il nome di code page, e ogni pagina è indicata da un identificativo numerico. Per esempio, la pagina contenente le lettere accentate è la numero 850, mentre quella dedicata all’alfabeto greco è la numero 737.
Con l’avvento di Windows, Microsoft introdusse delle nuove codifiche, sempre basate su code page. Per distinguerle da quelle OEM usate in ambiente DOS le denominò, piuttosto infelicemente, ANSI: di fatto, non esiste alcuno standard ANSI a riguardo. Microsoft scelse quel nome perché nel definire quelle codifiche si ispirò – piuttosto liberamente, in realtà – a una proposta di standard che però non fu mai preso in considerazione dal comitato ANSI, e che qualche anno più tardi sarebbe divenuto l’ISO-8859-1 (cfr. voce “ANSI” in[7]). A titolo d'esempio, la code page ANSI Windows-1250 e lo standard ISO-8859-1 (noto anche come Latin 1) differiscono per ben 27 associazioni[9].
Poiché tutte le codifiche basate su code page estendono quella ASCII, un flusso di testo ASCII è perfettamente decodificabile utilizzando una qualunque codifica estesa; viceversa, un testo contenente caratteri specifici di una particolare code page OEM o Windows può essere decodificato correttamente solo tramite la medesima code page. Questo può rappresentare un problema, se il testo viene decodificato su un sistema che usa una code page diversa da quello che l’ha codificato (cfr. mojibake).
Una codifica basata su unità a 8 bit che rappresenta ogni carattere con un’unità può rappresentare al più 256 caratteri diversi. In alcuni ambiti questo limite è inaccettabile: le lingue asiatiche ne sono un esempio. Il problema viene normalmente risolto aumentando il numero di unità di codifica utilizzate per rappresentare un singolo carattere. Nel caso si scelga di utilizzare due unità di codifica per carattere (due byte), si parla di set di caratteri DBCS – Double-byte character set. In questo modo, il numero di caratteri rappresentabili sale a 65536. Alcune codifiche DBCS adottano particolari schemi di codifica mista, nelle quali alcuni caratteri vengono associati ad un’unica unità di codifica, altri a due: alla diminuzione del numero di simboli rappresentabili corrisponde però la possibilità di mantenere la compatibilità con la codifica ASCII per i caratteri che la prevedono. Queste codifiche non sono compatibili con quelle basate su code page, e ciò preclude la possibilità di scambiare informazioni testuali tra due sistemi che adottano due schemi diversi; spesso, anche questa situazione è inaccettabile.
Unicode (originariamente Unification Code) nasce negli Stati Uniti nella seconda metà degli anni ‘80 dallo sforzo congiunto di Xerox e IBM di definire un set di caratteri multi-lingua facilmente trattabile dai calcolatori, in particolare astraendo dalle code page e scegliendo un’unità di codifica a lunghezza fissa di 16 bit. I limiti di questa scelta si palesarono pochi anni più tardi, quando il processo di standardizzazione delle lingue orientali produsse un catalogo di decine di migliaia ideogrammi: un alfabeto troppo ampio per poter essere interamente rappresentato con una codifica a 16 bit.
Contemporaneamente in Europa nasceva lo standard ISO/IEC 10646, che introduceva una codifica a 32 bit nota come UCS – Universal (Coded) Character Set che, prevedendo delle sequenze di escape per attivare delle modalità di codifica differenti, rendeva praticamente infinito il numero di caratteri rappresentabili.
Agli inizi degli anni ‘90 ebbe inizio un processo di unificazione delle due codifiche. Le attribuzioni dei codici della versione 6.1 di Unicode corrispondono perfettamente a quelle definite dallo standard ISO/IEC 10646:2012.
Ad ogni carattere Unicode assegna un code point, ovvero un valore numerico compreso tra 0 e 10FFFF cui viene applicato il prefisso U+ (il code point della lettera A maiuscola è U+0041). Non tutti i code point sono assegnati ad un carattere. La codifica è strutturata in piani, ognuno dei quali contiene 65536 caratteri. I piani definiti sono 16, e sono identificati dalle prime due cifre del codice esadecimale. I caratteri originali Unicode a 16 bit, i cui codici sono compresi tra 0 e 00FFFF, costituiscono il piano zero o BMP – Basic Multilingual Plan. I piani successivi sono detti piani estesi o piani surrogati. Se i caratteri del piano zero sono rappresentabili con 16 bit, quelli dei piani estesi necessitano di almeno 24 bit. Codificare caratteri con sequenze di lunghezza diversa può essere problematico, come già visto. D’altra parte, anche utilizzare più bit del necessario per rappresentare i caratteri ha i suoi svantaggi.
Piano | Intervallo | Descrizione | Abbreviazione |
---|---|---|---|
00 | 000000-00FFFF | Basic Multilingual Plane | BMP |
01 | 010000-01FFFF | Supplementary Multilingual Plane | SMP |
02 | 020000-02FFFF | Supplementary Ideographic Plane | SIP |
03 | 030000-03FFFF | Tertiary Ideographic Plane | TIP |
04-13 | 040000-0DFFFF | non assegnati | - |
14 | 0E0000-0EFFFF | Supplementary Special-purpose Plane | SSP |
15 | 0F0000-0FFFFF | Supplementary Private Use Area-A | - |
16 | 100000-10FFFF | Supplementary Private Use Area-B | - |
Il carattere in Unicode è un’entità astratta, indipendente dalla sua forma grafica (glifo); in questo senso, Unicode associa un code point alla descrizione di un carattere, senza entrare nel merito dell’aspetto (corsivo, grassetto, con o senza le grazie, …). Ad esempio, il code point U+0041 è associato al carattere LATIN CAPITAL LETTER A, ed è solo per motivi di praticità che nelle tabelle di codifica viene anche riportato un esempio di rappresentazione grafica del carattere.
Per ragioni di compatibilità, la codifica Unicode non è biunivoca: uno simbolo può essere identificato da differenti sequenze di caratteri; si parla in questo caso di caratteri equivalenti. Esistono due tipi di equivalenze:
U+00E8 - LATIN SMALL LETTER E WITH GRAVE..........è U+0300 - COMBINING GRAVE ACCENT...................̀ U+0065 - LATIN SMALL LETTER E.....................e U+00E8 (è) ≡ U+0300 U+0065 (̀e)
Un caso di equivalenza canonica ternaria:
U+00C5 - LATIN CAPITAL LETTER A WITH RING ABOVE...Å U+030A - COMBINING RING ABOVE.....................̊ U+0041 - LATIN CAPITAL LETTER A...................A U+212B - ANGSTROM SIGN............................Å U+00C5 (Å) ≡ U+030A U+0041 (̊A) ≡ U+212B (Å)
U+2460 CIRCLED DIGIT ONE..........................① U+0031 DIGIT ONE..................................1 U+2460 (①) ∼ U+0031 (1) U+2083 - SUBSCRIPT THREE..........................₃ U+0033 - DIGIT THREE..............................3 U+2083 (₃) ∼ U+0033 (3) U+FB01 - LATIN SMALL LIGATURE FI..................fi U+0066 - LATIN SMALL LETTER F.....................f U+0069 - LATIN SMALL LETTER I.....................i U+FB01 (fi) ∼ U+0066 U+0069 (fi)
Poiché uno stesso carattere può assumere codifiche differenti, nasce il problema di stabilire, dati due testi Unicode, se questi sono uguali. A tal scopo sono state definite delle regole di normalizzazione delle rappresentazioni dei testi Unicode – Unicode Normalization Forms[10] – che si occupano di trasformare i testi originali in una forma primitiva comune, a partire dalla quale risulta più semplice effettuare il confronto.
Se da una parte Unicode associa ad ogni carattere un codice univoco, dall’altra offre la possibilità di scegliere il formato di rappresentazione binaria dei codici (UTF – Unicode/UCS Transformation Format), variabile o fissa:
UTF-8: codifica multi-byte a lunghezza variabile con unità di codifica a 8 bit, ASCII-compatibile; rappresenta ogni carattere con un minimo di 1 ad un massimo di 4 byte. E’ una codifica stateless, ovvero gli errori di codifica di un carattere non si propagano ai successivi;
UTF-16: codifica multi-byte a lunghezza variabile con unità di codifica a 16 bit; ogni carattere è rappresentato con una sequenza di 2 o 4 byte. Esiste nelle varianti big endian (UTF-16BE) e little endian (UTF-16LE);
UTF-32: codifica multi-byte a lunghezza fissa con unità di codifica a 32 bit; ogni carattere è rappresentato con una sequenza di 4 byte. Esiste nelle varianti big endian (UTF-32BE) e little endian (UTF-32LE).
La tabella sottostante riporta il numero di byte necessario per rappresentare un code point nei vari formati:
Intervallo | Caratteri contenuti | UTF-8 | UTF-16 | UTF-32 |
---|---|---|---|---|
000000-00007F | Basic Latin | 1 | 2 | 4 |
000080-0007FF | Latin Extensions, Greek, Cyrillic, … | 2 | 2 | 4 |
000800-00FFFF | Thai, Hiragana, Katakana, … | 3 | 2 | 4 |
010000-10FFFF | Cuneiform, Hieroglyphs, Private, … | 4 | 4 | 4 |
Quando una sequenza di caratteri Unicode in formato UTF-16 o UTF-32 viene serializzata in un flusso di byte, per esempio un file, è necessario definire l'ordine di scrittura dei byte che costituiscono le singole unità di codifica. La rappresentazione UTF-16 della lettera A maiuscola, 0041, può ad esempio essere serializzata in due modi distinti: 00–41 oppure 41–00. La prima forma, presentando prima il byte più significativo seguito da quello meno significativo, viene detta big-endian; la seconda forma, nella quale il byte meno significativo precede quello più significativo, viene denominata big-endian. L'endianness indica quindi il “peso” del primo byte che si incontra.
La codifica UTF-8 distribuisce i bit di un code-point in una sequenza di byte secondo lo schema seguente:
Intervallo | Bits | Maschera |
---|---|---|
000000-00007F | 7 | 0xxxxxxx |
000080-0007FF | 11 | 110xxxxx 10xxxxxx |
000800-00FFFF | 16 | 1110xxxx 10xxxxxx 10xxxxxx |
010000-10FFFF | 21 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
La configurazione dei bit più significativi dei byte costituenti di una codifica UTF-8 assumono dunque un significato ben preciso:
Bits | Significato |
---|---|
0xxxxxxx | Byte iniziale di una sequenza di lunghezza 1 (ASCII) |
10xxxxxx | Byte successivo al primo |
110xxxxx | Byte iniziale di una sequenza di lunghezza 2 |
1110xxxx | Byte iniziale di una sequenza di lunghezza 3 |
11110xxx | Byte iniziale di una sequenza di lunghezza 4 |
Segue un esempio di codifica UTF-8:
U+2135 - ALEF SYMBOL..............................ℵ 2135 → 0010 0001 0011 0101 0010 0001 0011 0101 → 0010 000100 110101 ↓ ↓ ↓ 1110xxxx 10xxxxxx 10xxxxxx ↓ ↓ ↓ 11100010 10000100 10110101 ↓ ↓ ↓ E2 84 B5 U+2135 → E2 84 B5
La codifica UTF-16 fa uso di alcuni speciali code-point del Basic Multilingual Plan, denominati surrogati, per indirizzare i caratteri al di fuori del BMP stesso. I surrogati sono 2048 code-point che non corrispondono a nessun carattere, e si suddividono in surrogati alti, i cui codici vanno da U+D800 fino a U+DBFF, e surrogati bassi, da U+DC00 a U+DFFF. I surrogati appaiono sempre in coppia, nell'ordine alto/basso.
La coppia di surrogati viene determinata sottraendo 10000 dal code-point del carattere così da normalizzarlo nell'intervallo [0, FFFFF]; i venti bit rimanenti vengono equamente distribuiti nei dieci bit meno significativi del surrogato alto (D800÷DBFF ↔ 110110xxxxxxxxxx) e quello basso (DC00÷DFFF ↔ 110111xxxxxxxxxx):
U+1D54A - MATHEMATICAL DOUBLE-STRUCK CAPITAL S....𝕊 1D54A → 0001 1101 0101 0100 1010 - 0001 0000 0000 0000 0000 ======================== 0000 1101 0101 0100 1010 ↓ ↓ 0000110101 0101001010 ↓ ↓ 110110xxxxxxxxxx ↓ ↓ ↓ 1101100000110101 ↓ ↓ ↓ D835 ↓ 110111xxxxxxxxxx ↓ 1101110101001010 ↓ DD4A U+1D54A → D835 DD4A
Anche l’ISO/IEC 10646 specifica delle forme di codifica:
UCS-2: “Universal Character Set coded in 2 octets”, noto anche come “the two-octet BMP form”, rappresenta i soli caratteri del piano zero, ovvero quei caratteri rappresentabili con un’unica unità di 32 bit. In questo contesto, è compatibile con UTF-16. E’ stato dichiarato obsoleto nel 2011;
UCS-4: “Universal Character Set coded in 4 octets”, equivale a UTF-32, ed è la forma canonica di rappresentazione dei caratteri.
Il BOM – Byte order mark è il carattere Unicode U+FEFF quando utilizzato per specificare l’endianness di una sequenza di caratteri Unicode. Tale carattere, denominato “zero width no-break space”, può occupare solo la prima posizione di una sequenza di caratteri Unicode e non ha altro effetto che quello di fornire un’indicazione indiretta del tipo di ordinamento in uso — perdendo quindi il significato originale di “zero width no-break space”.
Nota: il vincolo di poter apparire solo in prima posizione è stato introdotto nella versione 3.2 di Unicode. Non è quindi escluso che si possa trovare anche in posizioni intermedie, dove di nuovo assume il significato di “zero width no-break space”. In questo contesto andrebbe sostituito dal carattere U+2060 “word joiner”.
Codifica | Endianness | BOM ammesso? |
---|---|---|
UTF-8 | n/a | sì |
UTF-16 | ? | sì |
UTF-16BE | big | no |
UTF-16LE | little | no |
UTF-32 | ? | sì |
UTF-32BE | big | no |
UTF-32LE | little | no |
Utilizzando unità di codifica a 8 byte, in linea di principio la presenza del BOM è superflua; alcuni software tuttavia la potrebbero richiedere. Il BOM in UTF-8 è rappresentato dalla sequenza EF BB BF.
Se durante la decodifica di un flusso Unicode la prima coppia di byte incontrata è FE FF, si può presupporre che si tratti del BOM di una sequenza big-endian. Se invece si trova FF FE, essendo il code point U+FFFE non valido, si può presupporre che si tratti del BOM di una sequenza little-endian. In genere, se un flusso binario inizia con la sequenza FF FE o FE FF si può ragionevolmente presupporre che si tratti di testo Unicode UTF-16.
Valgono le stesse considerazioni fatte per l’UTF-16.
Il mojibake si verifica quando, a causa dell'adozione di una codifica errata, il testo non viene visualizzato correttamente. Ad esempio, la parola caffè, che corrisponde alla sequenza di code-point Unicode:
caffè → U+0063 U+0061 U+0066 U+0066 U+00E8
se codificata in UTF-8 origina la sequenza di byte:
caffè → 63 61 66 66 C3 A8
Questa stessa sequenza, se decodificata in ISO-8859-1, dà luogo alla “parola”:
63 61 66 66 C3 A8 → caffè
Viceversa, la codifica ISO-8859-1 della parola originale corrisponde a:
caffè → 63 61 66 E8
che decodificata in UTF-8 produce:
63 61 66 E8 → caff�
Il carattere �, noto anche come REPLACEMENT CHARACTER, è il simbolo convenzionalmente utilizzato da Unicode per segnalare la presenza di un code-point sconosciuto o non rappresentabile.
In Python 3.x, la cui conformità a Unicode è rimarchevole, accade questo (font permettendo):
>>> int("৪୨") 42
La risposta risulta meno sorprendente se si considera l'espressione seguente, equivalente alla prima:
>>> int("\u09ea\u0b68") 42
La consultazione delle tabelle Unicode risolve il mistero:
U+09EA - BENGALI DIGIT FOUR.......................৪ U+0B68 - ORIYA DIGIT TWO..........................୨
Aggiornamento [28/05/2014]
Al C++Now 2014 c'è stato un intervento di James McNellis intitolato “Unicode in C++”; le slide che ha preparato sono splendide, tanto che ne ho fatto una copia locale per non rischiare di perderle!
Cimarosti, M. “Dodici anni di Unicode” — http://web.tiscali.it/marco.cimarosti/pro_graf.html, visitato il 07/06/2013
Kuhn, M. “UTF-8 and Unicode FAQ for Unix/Linux” — http://www.cl.cam.ac.uk/~mgk25/unicode.html, visitato il 05/06/2013
Searle, S. “A Brief History of Character Codes” — http://tronweb.super-nova.co.jp/characcodehist.html
Spolsky, J. “The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)”, joelonsoftware.com — http://www.joelonsoftware.com/articles/Unicode.html, visitato il 05/06/2013
“A Technical Introduction”, The Unicode Consortium — http://www.unicode.org/standard/principles.html, visitato il 07/06/2013
“ASCII format for Network Interchange” — http://tools.ietf.org/rfc/rfc20.txt, visitato il 05/06/2013
“Global Development Center’s Glossary”, microsoft.com — http://msdn.microsoft.com/en-us/goglobal/bb964658.aspx#a, visitato il 05/06/2013
“Microsoft OEM (DOS) Code Pages”, Character Sets And Code Pages At The Push Of A Button — http://msdn.microsoft.com/en-us/goglobal/bb964658.aspx#a, visitato il 05/06/2013
“Windows-1252”, wikipedia.org — https://en.wikipedia.org/wiki/Windows-1252, visitato il 05/06/2013
“Unicode Standard Annex #15 - UNICODE NORMALIZATION FORMS”, The Unicode Consortium — http://unicode.org/reports/tr15/, visitato il 07/06/2013
“UTF-8 encoding table and Unicode characters”, utf8-chartable.de — http://www.utf8-chartable.de/unicode-utf8-table.pl
“UTF-8 Sampler”, columbia.edu — http://www.columbia.edu/~fdc/utf8/
Pagina modificata il 03/07/2013