risorse | unicode

Da ASCII a 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 ASCIIAmerican Standard Code for Information Interchange.

ASCII

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 Mask600001111
500110011
401010101
 Col→01234567
3210Row↓ 
00000NULDLESP0@P`p
00011SOHDC1!1AQaq
00102STXDC2"2BRbr
00113ETXDC3#3CScs
01004EOTDC4$4DTdt
01015ENQNAK%5EUeu
01106ACKSYN&6FVfv
01117BELETB'7GWgw
10008BSCAN(8HXhx
10019HTEM)9IYiy
1010ALFSUB*:JZjz
1011BVTESC+;K[k{
1100CFFFS,<L\l
1101DCRGS-=M]m}
1110ESORS.>N^n~
1111FSIUS/?O_oDEL

È 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

Dall’ASCII alle code page (OEM)

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 OEMOriginal 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.

Code page OEM e ANSI

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].

Ambiguità delle code page

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).

Codifiche composte

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 DBCSDouble-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

Premessa storica

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 UCSUniversal (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.

Code point e piani

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 BMPBasic 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.

PianoIntervalloDescrizioneAbbreviazione
00000000-00FFFFBasic Multilingual PlaneBMP
01010000-01FFFFSupplementary Multilingual PlaneSMP
02020000-02FFFFSupplementary Ideographic PlaneSIP
03030000-03FFFFTertiary Ideographic PlaneTIP
04-13040000-0DFFFFnon assegnati-
140E0000-0EFFFF Supplementary Special-purpose PlaneSSP
150F0000-0FFFFFSupplementary Private Use Area-A-
16100000-10FFFFSupplementary Private Use Area-B-

Caratteri

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.

Equivalenza

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:

equivalenza canonica
caratteri o sequenze di caratteri che rappresentano lo stesso simbolo astratto e che sono graficamente indistinguibili; un esempio sono i caratteri ottenibili per composizione:
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 (Å)
equivalenza di compatibilità
caratteri o sequenze di caratteri che rappresentano lo stesso simbolo astratto ma che si distinguono per forma o altre caratteristiche; sono comprese le varianti grafiche, gli apici, i pedici, le legature, …:
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.

Separazione tra codifica e rappresentazione binaria

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 (UTFUnicode/UCS Transformation Format), variabile o fissa:

La tabella sottostante riporta il numero di byte necessario per rappresentare un code point nei vari formati:

IntervalloCaratteri contenutiUTF-8UTF-16UTF-32
000000-00007FBasic Latin124
000080-0007FFLatin Extensions, Greek, Cyrillic, …224
000800-00FFFFThai, Hiragana, Katakana, …324
010000-10FFFFCuneiform, Hieroglyphs, Private, …444

Endianness

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: 0041 oppure 4100. 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.

Peculiarità della codifica UTF-8

La codifica UTF-8 distribuisce i bit di un code-point in una sequenza di byte secondo lo schema seguente:

IntervalloBitsMaschera
000000-00007F70xxxxxxx
000080-0007FF11110xxxxx 10xxxxxx
000800-00FFFF161110xxxx 10xxxxxx 10xxxxxx
010000-10FFFF2111110xxx 10xxxxxx 10xxxxxx 10xxxxxx

La configurazione dei bit più significativi dei byte costituenti di una codifica UTF-8 assumono dunque un significato ben preciso:

BitsSignificato
0xxxxxxxByte iniziale di una sequenza di lunghezza 1 (ASCII)
10xxxxxxByte successivo al primo
110xxxxxByte iniziale di una sequenza di lunghezza 2
1110xxxxByte iniziale di una sequenza di lunghezza 3
11110xxxByte 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

Peculiarità della codifica UTF-16

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÷DBFF110110xxxxxxxxxx) e quello basso (DC00÷DFFF110111xxxxxxxxxx):

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

Formato UTF e UCS

Anche l’ISO/IEC 10646 specifica delle forme di codifica:

Il BOM

Il BOMByte 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”.

CodificaEndiannessBOM ammesso?
UTF-8n/a
UTF-16?
UTF-16BEbigno
UTF-16LElittleno
UTF-32?
UTF-32BEbigno
UTF-32LElittleno

Il BOM nell’UTF-8

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.

Il BOM nell’UTF-16

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.

Il BOM nell’UTF-32

Valgono le stesse considerazioni fatte per l’UTF-16.

Mojibake

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.

Curiosità

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!

Riferimenti

  1. Cimarosti, M. “Dodici anni di Unicode”http://web.tiscali.it/marco.cimarosti/pro_graf.html, visitato il 07/06/2013

  2. Kuhn, M. “UTF-8 and Unicode FAQ for Unix/Linux”http://www.cl.cam.ac.uk/~mgk25/unicode.html, visitato il 05/06/2013

  3. Searle, S. “A Brief History of Character Codes”http://tronweb.super-nova.co.jp/characcodehist.html

  4. 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

  5. “A Technical Introduction”, The Unicode Consortium — http://www.unicode.org/standard/principles.html, visitato il 07/06/2013

  6. “ASCII format for Network Interchange”http://tools.ietf.org/rfc/rfc20.txt, visitato il 05/06/2013

  7. “Global Development Center’s Glossary”, microsoft.com — http://msdn.microsoft.com/en-us/goglobal/bb964658.aspx#a, visitato il 05/06/2013

  8. “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

  9. “Windows-1252”, wikipedia.org — https://en.wikipedia.org/wiki/Windows-1252, visitato il 05/06/2013

  10. “Unicode Standard Annex #15 - UNICODE NORMALIZATION FORMS”, The Unicode Consortium — http://unicode.org/reports/tr15/, visitato il 07/06/2013

  11. “UTF-8 encoding table and Unicode characters”, utf8-chartable.de — http://www.utf8-chartable.de/unicode-utf8-table.pl

  12. “UTF-8 Sampler”, columbia.edu — http://www.columbia.edu/~fdc/utf8/

Pagina modificata il 03/07/2013