


































Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Gli argomenti presenti in questi appunti offrono una panoramica sulle basi teoriche della Compressione Dati, ed in particolare: - una visione strutturata dei principali metodi e tecniche e degli standard di compressione dati; - una panoramica dello stato dell'arte - utilizzo delle tecniche di compressione dati nella trasmissione e memorizzazione di file digitali.
Tipologia: Appunti
1 / 42
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!



































In offerta
Il corso è diviso in due parti: nella prima parte si affronteranno le lezioni teoriche, nella seconda parte si organizzerà il progetto. La modalità d’esame prevede appunto delle domande sulla prima parte e l’esposizione del progetto. Il progetto riguarda l’approfondimento su un argomento di compressione dati che si trovano "al confine" tra lo stato dell’arte e la ricerca, nel senso che è possibile ampliare un progetto portato in passato aggiungendo nuovi algoritmi oppure partire ex novo da un argomento nuovo, andando a cercare materiale su articoli, pubblicazioni, ecc. Un buon progetto è diviso in tre parti:
I gruppi devono essere di 4 o 5 persone.
La compressione dati si è evoluta come sottoinsieme del campo di Teoria dell’Informazione diventando poi un campo a sé stante.
Un alfabeto è un insieme finito di caratteri contenente almeno un elemento. Gli elementi di un alfabeto sono chiamati caratteri. Una stringa su un alfabeto è una sequenza di caratteri dell’alfabeto.
Definizione: Sia Σ = {s 1... sk}, k ≥ 1 un alfabeto. Una sorgente è un processo che sequenzialmente produce caratteri di Σ. Σ è chiamato alfabeto sorgente. Una sorgente è detta del primo ordine se le probabilità indipendenti di emissione p 1... pk, che danno come somma 1 , sono associate con il corrispondente elemento di Σ. Di norma un carattere si avrà probabilità pi di essere il prossimo carattere che viene trasmesso.
Nelle sorgenti del primo ordine l’emissione del carattere i-esimo non dipende dai caratteri che sono stati trasmessi precedentemente. Vediamo ora due esempi di sorgente che possono tornare utili.
È definita su un certo alfabeto Σ = {s 1... sk}, e nonostante sia k ≥ 1 abbiamo che all’interno di questo alfabeto viene scelto sempre lo stesso carattere come prossimo carattere da trasmettere. Detto si questo carattere, avremo che la probabilità pi sarà sempre 1 , mentre per ogni sj con j 6 = i avremo che pj = 0.
Questa sorgente ha il minimo grado di "sorpresa", ovvero un osservatore esterno che conosce il tipo di sorgente non sarà sorpreso a veder trasmettere sempre lo stesso carattere.
È definita anch’essa su un alfabeto Σ = {s 1... sk}, con k ≥ 1. Il prossimo carattere che viene emesso viene scelto in modo uniforme e indipendente tra tutti i caratteri, quindi abbiamo che per ogni si, pi = (^1) k. L’osservatore non potrà fare nessuna predizione sul prossimo carattere perché tutti i caratteri hanno la stessa probabilità di emissione. Questa sorgente ha il livello di "sorpresa" più alto.
2.2 Entropia
Possiamo identificare il concetto di sorpresa visto finora con il concetto di entropia, espresso formal- mente di seguito.
Definizione: Sia k ≥ 1 un intero e sia S una sorgente di primo ordine che genera i caratteri dall’alfabeto
Σ = {s 1... sk}
con probabilità indipendenti p 1... pk. L’entropia di S in base r, r > 1 , è data da:
Hr (S) =
∑^ k
i=
pi logr
pi
Quando la base non è specificata assumiamo che sia 2, in quanto ragioniamo in logica binaria, per cui abbrevieremo H 2 (S) come H(S). Questa informazione mi dice che per comprimere un oggetto ho bisogno di almeno H(S) bit se non voglio incorrere in errori. Inoltre è sempre possibile costruire uno schema di codifica che codifica l’output di quella sorgente usando un numero di bit pari all’entropia. Questo è indicato nel Teorema fondamentale della codifica sorgente:
Teorema: Sia S una sorgente di primo ordine su un alfabeto Σ e sia Γ un alfabeto di r > 1 caratteri. Codificare i caratteri di S con i caratteri di Γ richiede una media di Hr (S) caratteri di Γ per ogni carattere di Σ. Inoltre, per ogni numero reale > 0 esiste uno schema di codifica che usa in media Hr (S) + caratteri di Γ per ogni carattere di Σ.
2.3 Sorgenti di ordine i-esimo
Raramente in scenari reali le sorgenti sono del primo ordine. Tipicamente la probabilità che la sorgente emetta un carattere dipende dai caratteri che ha emesso in passato. Ad esempio in un file di testo, consideriamo una sorgente che emette parole italiane. Se troviamo il carattere Q allora è molto probabile che il carattere successivo sia una U. Questa osservazione porta alla definizione teorica di sorgente di ordine i-esimo, con i ≥ 1 , dove i denota il numero di caratteri dal quale il carattere che viene emesso dipende. Ad esempio, in una sorgente di ordine 2 il prossimo carattere dipende dal precedente, in una sorgente di ordine 3 il prossimo carattere dipende dai due precedenti, e così via.
2.4 Considerazioni importanti
L’importanza dell’entropia nella compressione dati ci dà un lower bound per la compressione che possiamo ottenere. Per una certa sorgente quindi esisterà una certa quantità detta entropia sotto cui non possiamo scendere per la compressione. Tipicamente gli algoritmi di compressione lossless che riescono a comprimere al limite dell’entropia sono detti ottimali. Vedremo che le considerazioni che facciamo sulle compressioni vanno applicate nella pratica a sorgenti finite che emettono una quantità finita di bit, e non a sorgenti infinite. Possiamo quindi elencare tre importanti considerazioni che vanno fatte:
Teoricamente qualsiasi metodo di compressione dati può essere visto come esempio di codifica. In realtà ci renderemo conto che le tecniche standard di codifica occorrono ad un livello più basso rispetto ai metodi di compressione. Un esempio di codifica^1 molto comune è il codice a blocchi k-ario, che mappa ciascun elemento di un insieme finito S di n elementi in una stringa di lunghezza dlogk(n)e su un alfabeto di dimensioni k. Supponiamo quindi di voler codificare l’output di una sorgente su un certo alfabeto Σ in un alfabeto binario nel modo più efficiente possibile. In questo caso utilizzando la codifica appena vista avrò bisogno di dlog 2 (n)e bit per codificare ciascun simbolo di Σ. Un altro esempio è dato dalla codifica ASCII, che è un codice a blocchi binario che mappa ognuno dei 128 caratteri ASCII in una stringa unica binaria di 8 bit, dove il primo bit è sempre 0.
Definizione: Data una sorgente S e un alfabeto Σ, una codifica da S a Σ è una funzione f che mappa ogni elemento di S a una stringa non vuota su Σ.
Quindi se ho una sorgente S che emette simboli su un alfabeto k-ario e voglio codificarlo su Σ che è un alfabeto binario, codificare S in binario significa prendere ciascun elemento che può essere emesso da S e codificarlo con una stringa di simboli binari. Il range di f è chiamato insieme delle parole codice di f. Le parole codice della funzione f nell’esempio precedente saranno tutte le stringhe binarie che codificano elementi di S. Una funzione f è iniettiva se non mappa mai due elementi nella stessa stringa. Ogni codifica f può essere estesa a qualsiasi lista finita di elementi definendo:
f (s 1 ,... , sk) =
∏^ k
i=
f (si)
dove la produttoria indica la concatenazione di stringhe. Ad esempio, se voglio codificare a, b, c ∈ S, avrò che f (a, b, c) sarà la concatenazione della codifica di a, della codifica di b e della codifica di c.
(^1) Useremo i termini codifica e codice in maniera intercambiabile.
3.2 Codifica univocamente decifrabile
Dato che per ora stiamo parlando principalmente di compressione lossless, dovremo assumere che le funzioni di codifica siano iniettive laddove non specificato. Tuttavia, lavorare con funzioni iniettive non assicura la decodificabilità univoca di quello che viene codificato. esserci condizioni in cui date due liste L 1 e L 2 si verifica che f(L 1 ) = (L 2 ). Questo ci porta a dare la seguente
Definizione: Sia f una codifica da un insieme S a un alfabeto Σ. Una stringa α su Σ è univocamente decifrabile rispetto a f se vi è al più una lista L di elementi di S tali che f (L) = α. Inoltre diremo che f è univocamente decifrabile se tutte le stringhe su Σ sono univocamente decifrabili rispetto a f.
Esempio: Sia S = {a, b, c, d, e}, Σ = { 0 , 1 } e sia f definita come segue:
f (a) = 00
f (b) = 01
f (c) = 10
f (d) = 11
f (e) = 100
Chiaramente, f (a, b, c, d, e) = 00011011100. Inoltre, possiamo verificare che la stringa di bit 00011011100 è univocamente decifrabile rispetto a f. Tuttavia, non tutte le stringhe binarie sono univocamente decifrabili rispetto a f. Infatti, f (cba) = f (ee) = 100100.
3.3 Disuguaglianza di Kraft/MacMillian
Una domanda che ci viene naturale per ogni insieme finito S e per ogni alfabeto Σ è, se le lunghezze delle parole codice sono specificate a priori, quali sono le condizioni necessarie e sufficienti su queste lunghezze per assicurarci che esista una codifica univocamente decifrabile da S a Σ.
Il problema è il seguente: ho una sorgente S con un certo alfabeto sorgente di k simboli, e un alfabeto di codifica Σ (per semplicità pensiamo all’alfabeto binario). Mi vengono date in anticipo delle lunghezze per quanto riguarda le parole codice, quindi nel mio caso avrò k lunghezze perché ho k simboli sorgente. Ad esempio voglio codificare il primo simbolo con 2 bit, il secondo simbolo con 1 bit, il terzo con 3 bit, e così via. Quali sono le condizioni necessarie e sufficiente su queste lunghezze che mi vengono date in anticipo per far sì che esista una codifica univocamente decifrabile da S a Σ? Il teorema seguente, detto appunto disuguaglianza di Kraft/MacMillian, ci dà queste condizioni.
Teorema: Siano S e Σ rispettivamente un insieme finito di simboli e un alfabeto di simboli. Se le parole codice sono vincolate ad avere lunghezza l 1 ,... , l|S|, allora una condizione necessaria e sufficiente affinché esista una codifica univocamente decifrabile da S a Σ è:
∑^ |S|
i=
|Σ|li
3.4 Codici prefissi
Definizione: Una codifica da un insieme S ad un alfabeto Σ è un codice prefisso se nessuna parola codice è prefisso di un’altra parola codice.
Algoritmo (1) Initialize F OREST to have a 1-node trie Tx for each element x of S. Set weight(Tx) = px,
(2) while |F OREST | > 1 do begin
Let Y and Z be the two tries in F OREST of lowest weight (resolve ties arbitrarily). Combine Y and Z by creating a new root r with weight weight(Y ) + weight(Z) that is attached to one of Y and Z via a 0 and to the other via a 1 (the order doesn’t matter). end
L’algoritmo di Huffman parte da una foresta di alberi. Inizializziamo questa foresta di alberi in maniera tale che abbia tanti alberi di un singolo nodo quanti sono i caratteri della sorgente S, e per ogni x il peso di quell’albero Tx sarà posto uguale alla probabilità px. Poi c’è il ciclo che costruisce man mano l’albero di codifica: finché la foresta ha più di un albero, si prendono i due alberi che hanno peso più basso (e quindi hanno associato il carattere che ha probabilità di emissione più bassa), si combinano in un unico albero creando una nuova radice r con peso uguale alla somma dei pesi dei due alberi, e che ha attaccati come figli i due alberi scelti, etichettati rispettivamente con 0 e con 1. Questo ciclo continua finché non si ottiene un singolo albero binario, che sarà il nostro albero di Huffman.
Esempio: Supponiamo di voler costruire un albero di Huffman dati i seguenti simboli e la loro frequenza:
15 7 6 6 5 A B C D E
Il primo passo è quello di costruire una foresta contenente i cinque alberi. Dopodiché prendiamo i due alberi con peso minore, ovvero quelli dei simboli D e E, e li combiniamo in un albero la cui nuova radice avrà peso 11 (6+5), ed etichettiamo i rami con 0 e 1.
D E
6 5
0 11 1
Facciamo la stessa cosa e prendiamo i due alberi con probabilità minore. In questo caso sono B e C, per cui costruiamo un nuovo albero con peso 13 (7+6), che si aggiunge alla foresta, che ora è:
A B C
15 7 6
0 13 1
D E
6 5
0 11 1
Continuiamo nel ciclo e prendiamo i due alberi con peso minore, quindi 13 e 11, e costruiamo un altro albero che avrà peso 24. Il risultato è il seguente:
A B C
15 7 6
0 13 1
D E
6 5
0 11 1
0 1 24
A questo punto restano due alberi, quelli con peso 15 e 24. Costruiamo allora l’albero finale:
A B C
15 7 6
0 13 1
D E
6 5
0 11 1
0 1 24
0 39 1
Per determinare la codifica per ogni simbolo, basta percorrere l’albero dalla radice fino al simbolo desiderato. Per cui la codifica finale sarà:
A 0 B 100 C 101 D 110 E 111
Abbiamo inoltre costruito un codice prefisso e quindi univocamente decifrabile, perché nessuna parola codice sarà prefissa di un’altra: quando arrivo a una foglia mi fermo, non costruisco altre parole codice a partire da quella foglia. Inoltre, possiamo notare che i simboli che hanno più probabilità sono codificati con stringhe di bit più corte, in particolare A, che aveva probabilità maggiore, è codificata con una stringa lunga 1 bit. Per questo motivo la codifica di Huffman applica una compressione migliore rispetto a un codice a blocchi k-ario standard.
La codifica di Huffman è molto utilizzata in applicazioni di compressione ben note, ad esempio in alcune versioni dell’algoritmo JPEG. Inoltre questa codifica è detta ottimale nel senso della Teoria dell’informazione, ovvero se ho un flusso infinito di simboli di un alfabeto sorgente e li codifico con Huffman, allora ottengo una codifica che tocca il limite ottimo dettato dall’entropia. Uno dei limiti di questa codifica è che avrò sempre bisogno di almeno 1 bit per codificare qualsiasi carattere, quindi ad esempio se vogliamo codificare con Huffman immagini binarie non abbiamo nessun vantaggio. Spesso infatti si usa per codificare caratteri a gruppi piuttosto che singoli, per migliorarne l’efficienza. Ad esempio, se la probabilità di emissione di un carattere è di 1/3, il numero di bit ottimale
Quindi l’idea della codifica aritmetica è restringere sempre di più l’intervallo. Quando la stringa emessa dalla sorgente sarà finita, l’intervallo finale raggiunto indicherà la codifica della stringa che è stata emessa. Man mano che il codificatore riceve caratteri e restringe l’intervallo, manda informazioni al decodificatore che man mano riesce a ricostruire l’intervallo che è stato ristretto dal codificatore.
Codificatore e decodificatore riescono a lavorare in lockstep, per cui non c’è bisogno di assegnare un numero finito di bit ad ogni simbolo della sorgente. Visto che il codificatore invia un’informazione che rappresenta più di un simbolo, in realtà con 1 bit sarà possibile codificare più di un simbolo. La codifica aritmetica rimpiazza una stringa di simboli emessi dalla sorgente con un singolo numero in floating point. Ovviamente, più è complesso il messaggio, più avrò bisogno di numeri reali che abbiano una precisione maggiore e quindi più "grandi" da rappresentare. L’output di un codificatore aritmetico sarà un numero reale che è strettamente minore di 1 e maggiore o uguale a 0. Questo numero potrà essere decodificato univocamente per costruire la sequenza di simboli da cui è stato ricavato, a patto che il decodificatore sappia la lunghezza del messaggio.
Consideriamo una sorgente che emette la stringa BILL GATES,, con distribuzione di probabilità:
Carattere Probabilità spazio 1/ A 1/ B 1/ E 1/ G 1/ I 1/ L 1/ S 2/ T 1/
Una volta che conosciamo le probabilità sorgente, dobbiamo dividere la linea dei numeri reali in tante parti quante sono le lettere emesse dalla sorgente (quindi in 9 parti). Normalmente non importa in che ordine vado ad assegnare i simboli agli intervalli, l’importante è che siano proporzionati alla loro probabilità di emissione e che siano decodificati nello stesso ordine in cui sono stati codificati. Avremo quindi la seguente divisione:
Carattere Probabilità Range spazio 1/10 0. 00 ≤ r < 0. 10 A 1/10 0. 10 ≤ r < 0. 20 B 1/10 0. 20 ≤ r < 0. 30 E 1/10 0. 30 ≤ r < 0. 40 G 1/10 0. 40 ≤ r < 0. 50 I 1/10 0. 50 ≤ r < 0. 60 L 1/10 0. 60 ≤ r < 0. 80 S 2/10 0. 80 ≤ r < 0. 90 T 1/10 0. 90 ≤ r < 1. 00
La prima lettera che viene emessa dalla sorgente è la lettera B. Per andare a decodificarla, l’intervallo deve essere ristretto a [0. 20 , 0 .30). Quindi tutte le lettere successive saranno comprese in questo sotto- intervallo, così come il numero reale finale. Durante il resto del processo di codifica, ogni simbolo restringerà ulteriormente l’intervallo. Il prossimo carattere è la lettera I, il cui intervallo di riferimento
è [0. 50 , 0 .60). Per cui dividerò l’intervallo [0. 20 , 0 .30) in 9 sotto-intervalli proporzionali alle probabilità dei simboli, e sceglierò quello a cui fa riferimento il simbolo I, ovvero [0. 25 , 0 .26).
Una volta chiarita l’idea di fondo, scrivere l’algoritmo di codifica funzionante su stringhe di lunghezza arbitraria è relativamente semplice:
low = 0.0; high = 0.0; while (( c = getc ( input ) ) != EOF ) { range = high - low ; high = low + range * high_range ( c ) ; low = low + range * low_range ( c ) ; } output ( low ) ;
La tabella finale sarà la seguente:
Carattere Low High 0.0 1. B 0.2 0. I 0.25 0. L 0.256 0. L 0.2572 0. spazio 0.25720 0. G 0.257216 0. A 0.2572164 0. T 0.25721676 0. E 0.257216772 0. S 0.2572167752 0.
Il codificatore quindi potrà prendere un qualsiasi numero reale compreso tra 0.2572167752 e 0.2572167756, inviarlo al decodificatore, e il decodificatore conoscendo la lunghezza del messaggio riuscirà a de- codificare il messaggio. Per semplicità viene inviato il valore corrispondente all’estremo inferiore dell’intervallo.
Il codificatore controlla all’interno di quale intervallo cade il valore ricevuto. Il valore si trova tra 0.2 e 0.3, per cui capirà che la prima lettera è B. A questo punto deve rimuovere il contributo di B dal valore da decodificare per invertire il processo. Viene quindi sottratto il valore di B, che sappiamo essere 0.2. Il valore ottenuto è 0.0572167752. Ora si divide questo numero per la dimensione dell’intervallo di B, che è 1/10. Il numero che otteniamo è 0.572167752. Continuando il processo, vediamo che questo numero cade tra 0.5 e 0.6, che è l’intervallo della lettera I, per cui facciamo la stessa cosa: sottraiamo 0.5, ottenendo 0.072167752, e dividiamo per la lunghezza dell’intervallo di I (anche stavolta 1/10), ottenendo 0.72167752. Anche in questo caso indichiamo l’algoritmo di decodifica:
number = input_code () ; for ( ; ; ) { symbol = f i n d _ s y m b o l _ s t r a d d l i n g _ t h i s _ r a n g e ( number ) ; putc ( symbol ) ; range = high_range ( symbol ) - low_range ( symbol ) ; number = number - low_range ( symbol ) ; number = number / range ; }
In questo caso high_range valeva .30, per cui il nuovo valore di HIGH è 30000. Prima di salvare il dato dobbiamo decrementarlo per come abbiamo definito la nostra rappresentazione, per cui sarà 29999. Anche per LOW vale la stessa cosa, e infatti varrà 20000.
high : 29999 (999...) low : 20000 (000...)
A questo punto, la cifra più significativa delle due variabili è la stessa, ed è 2. Per cui qualsiasi numero vado a inviare al decompressore so che inizierà con 2 (0.2 in realtà). Dal punto di vista pratico si invia 2 al decompressore come output cumulativo e si fa uno shift a sinistra di 1 cifra di entrambe le variabili HIGH e LOW, per cui le nuove cifre più significative saranno rispettivamente 9 e 0. Continuando a seguire questo metodo per tutta la frase si ottiene il risultato mostrato nella tabella seguente.
High Low Range Output cumulativo Initial state 99999 00000 100000 Encode B (0.2 - 0.3 29999 20000 Shift out 2 99999 00000 10000. Encode I (0.5 - 0.6) 59999 50000. Shift out 5 99999 00000 100000. Encode L (0.6 - 0.8) 79999 60000 20000. Encode L (0.6 - 0.8) 75999 72000. Shift out 7 59999 20000 40000. Encode SPACE (0.0 - 0.1) 23999 20000. Shift out 2 39999 00000 40000. Encode G (0.4 - 0.5) 19999 16000. Shift out 1 99999 60000 40000. Encode A (0.1 - 0.2) 67999 64000. Shift out 6 79999 40000 40000. Encode T (0.9 - 1.0) 79999 76000. Shift out 7 99999 60000 40000. Encode E (0.3 - 0.4) 75999 72000. Shift out 7 59999 20000 40000. Encode S (0.8 - 0.9) 55999 52000. Shift out 5 59999 20000. Shift out 2. Shift out 0.
Questo schema funziona molto bene per codificare incrementalmente un messaggio, ma c’è un problema quando le cifre più significative di HIGH e LOW non corrispondono. Infatti se per un certo numero di volte non corrispondono, non riesco a completare la codifica perché non ho abbastanza precisione. Se la parola codificata ha una stringa di 0 o 9, i valori HIGH e LOW convergono lentamente allo stesso valore e andremo quindi a ridurre sempre di più l’intervallo. Se arrivo a un punto in cui HIGH vale 700004 e LOW vale 699995, l’intervallo calcolato sarà lungo solo una cifra, il che significa che la parola in output non avrà abbastanza precisione per essere codificata correttamente. Peggio ancora se arrivo in un punto in cui HIGH è 70000 e LOW è 69999, sono sicuro che nei prossimi passaggi non riuscirò a rappresentare questi valori con 5 cifre perché l’intervallo è diventato troppo piccolo.
È possibile risolvere questo problema di underflow facendo un altro controllo sulle cifre che non corri- spondono. In particolare se le cifre più significative di HIGH e LOW sono adiacenti (ovvero differiscono per 1) allora andiamo a vedere la seconda cifra: se sono 0 e 9 allora sappiamo che si verificherà un underflow. Cancelliamo quindi la seconda cifra sia da HIGH che da LOW e facciamo lo shift a sinistra del resto delle cifre. Quindi cancello 0 e 9 e faccio uno shift a sinistra per far si che HIGH e LOW ricevono un 9 e uno 0. La cifra più significativa rimane al suo posto. Usiamo una variabile chiamata underflow
counter che ci ricorda che abbiamo “buttato via” uno 0 o un 9, cosi quando andremo a inviare al decompressore il numero, dovremo ricordarci di mettere in quella posizione uno 0 o un 9. In tabella è mostrato questo processo:
Prima Dopo High: 40344 43449 Low: 39810 38100 Underflow: 0 1
Dopo ogni ricalcolo si controlla se le cifre più significative non combaciano per verificare se c’è stato un altro underflow. Visto che qui le cifre più significative non coincidono e quelle immediatamente successive sono 0 per HIGH e 9 per LOW, cancelliamo 0 e 9, incrementiamo l’underflow counter e facciamo shift a sinistra. È possibile provare che questa soluzione risolve il problema.
Così non è molto chiaro il motivo per cui questa codifica sia migliore rispetto a Huffman. Consideriamo allora questo esempio. Se dobbiamo codificare il flusso AAAAAAA, e la probabilità di A è 9/10, c’è un 90% di probabilità che il prossimo carattere emesso dalla sorgente sia la A. Impostiamo la nostra tabella di probabilità in modo che A occupi l’intervallo da .0 a. 9, e il simbolo di fine messaggio occupi l’intervallo da .9 a 1. Il processo di codifica è mostrato di seguito:
Nuovo carattere Low High 0.0 1. A 0.0 0. A 0.0 0. A 0.0 0. A 0.0 0. A 0.0 0. A 0.0 0. A 0.0 0. END 0.43046721 0.
Ora dobbiamo scegliere un numero per codificare il messaggio. Il numero .45 farà decodificare questo messaggio in modo univoco in "AAAAAAA". Queste due cifre decimali richiedono poco meno di 7 bit per la codifica, il che significa che abbiamo codificato 8 simboli in meno di 8 bit, mentre un messaggio con Huffman ottimale avrebbe richiesto un minimo di 9 bit.
Abbiamo visto queste due codifiche perché quando vedremo algoritmi di compressione per esempio JPEG o MPEG, scopriremo che anche se sono lossy hanno alla fine della loro compressione un passo che è relativo a una compressione ulteriore andando a comprimere in maniera lossless certe cose, usando Huffman o codifica aritmetica. In particolare JPEG fu introdotto come algoritmo che serviva per comprimere le foto digitali. Si decise che alla fine di JPEG ci voleva anche una fase entropica e si indicò nello standard due algoritmi possibili da usare nei compressori JPEG per la codifica entropica: Huffman o codifica aritmetica. A seconda dell’algoritmo utilizzato si parlava di compressione e decompressione JPEG Huffman/arithmetic coding. Arithmetic coding portava a una compressione maggiore ma non fu molto utilizzato per un problema di proprietà intellettuale, perché fu brevettata da dipendenti di IBM, la quale chiese una loyalty per permettere l’utilizzo di arithmetic coding, quindi tutti usavano Huffman. Quando IBM smise di chiedere le loyalty sull’utilizzo tutti passarono a arithmetic coding perché è più efficiente per la codifica entropica.