Informatica per script kiddies 16 – I perché e i come del sistema binario

Eccoci con il sedicesimo Informatica per script kiddies, che punta parecchio alle basi e spiega perché nell’informatica ci piacciono tanto gli zero e gli uno. Quest’articolo potrà essere un po’ troppo “di base” per molti di quelli che frequentano /r/ItalyInformatica, ma trovo possa essere utile e interessante per moltissimi di quelli che si interessano all’informatica, magari da un approccio di livello relativamente alto e da un percorso di studi senza troppa matematica.

Un po’ di basi (molto molto di base) sui segnali

I computer sono oggetti che devono memorizzare ed elaborare informazioni. Hanno quindi, come problema di base, quello di rappresentare queste informazioni in qualche modo che gli permetta sia di memorizzarle, sia di elaborarle.

Questa cosa, noi umani, le facciamo da millenni. Abbiamo dei linguaggi che hanno delle modalità di rappresentazione che permettono sia la rappresentazione che la trasmissione (i sistemi di scrittura), sia – quasi – solo la trasmissione (la lingua parlata).

Per le macchine, sia la lingua parlata che la scrittura sono cose estremamente difficili da trattare e lavorare, e sono molto lenti per struttura, specialmente la lingua parlata.

Dato che le macchine sono piuttosto brave a misurare cose, un buon modo di memorizzare informazioni potrebbe essere quello di dar loro qualcosa di misurabile, e associando le parti di informazione ad un singolo valore misurato. Ad esempio si potrebbe associare ogni lettera a una frequenza sonora (o elettrica), o a un colore, e dar loro la sequenza di frequenze o di colori.

Finché le informazioni sono composte da pochi simboli, o le macchine sono molto capaci, questa cosa è fattibile. Succede ad esempio nelle reti telefoniche, in cui il numero di telefono può essere inviato come sequenza di frequenze sonore, e succede in alcuni tipi di “codice a barre” come l’HCCB, in cui l’informazione è memorizzata anche sotto forma di colore.

Se però si vuole fare una cosa davvero semplice e versatile, ci si scontra con un problema abbastanza grande: misurare con precisione non è per niente semplice, e anche produrre cose misurabili sufficientemente precise è difficile. Produrre un suono ad un’esatta frequenza è difficile, leggere esattamente il valore emesso dalla fonte è difficile/impossibile, stampare proprio quell’esatto viola lì è molto poco fattibile. Si finisce quindi a dover emettere frequenze molto distanti fra loro (vedi il DTMF), o stampare pochi colori molto diversi tra loro (vedi l’HCCB).

Alla fine la cosa più semplice è avere un solo segnale, e la sua assenza. Due cose. Semplici, pulite. C’è corrente / non c’è corrente. È rosso / non è rosso. C’è un suono / non c’è un suono. C’è carta / c’è un buco.

Segnali di questo tipo sono facili da leggere, facili da memorizzare in tantissimi modi diversi (carica elettrica, polarità magnetica, carta perforata, interruttori accesi e spenti) e facile da elaborare dall’inizio della storia dell’informatica, ai tempi delle valvole (e ai fatti pure prima).

Per questo, si è scelto di fare così. I computer hanno un alfabeto fatto di due simboli, a cui si possono dare nomi a piacere. Li chiameremo zero e uno. Qualsiasi informazione pronta ad essere elaborata da un computer è rappresentata da sequenze di zero e di uno.

Un po’ di basi… sulle basi

Zero e uno non sono nomi a caso: quando c’è da elaborare cose bisogna avere sistemi per elaborarle, e il sistema che l’umanità si è inventato è la matematica. Ai matematici piacciono molto le cifre, e dovendo lavorare le cose con pochi simboli, utilizzano le prime cifre. Due simboli, le due prime cifre.

Come un po’ tutti sappiamo, di solito per fare i conti utilizziamo dieci cifre, da zero a nove. Se dobbiamo rappresentare numeri più grandi di nove, li costruiamo utilizzando più di una cifra. Ventinove, ad esempio, si rappresenta con due cifre, 29, che hanno un peso diverso: la prima, da sola, rappresenta il numero di quantità dieci contenute lì dentro, e la seconda rappresenta il numero di quantità uno. Una meraviglia chiara a tutti, ma che mi era utile ripetere, che chiamiamo notazione posizionale.

La notazione posizionale è una figata, tra le altre cose, perché non richiede affatto che i simboli siano dieci.

Proviamo a generalizzarla.

Se i simboli sono dieci, il simbolo più a destra rappresenta il numero di quantità uno (100) presenti nel numero. Quella alla sua sinistra rappresenta il numero di quantità dieci (101), quella ancora a destra il numero di quantità cento (102), e così via andando avanti con le potenze di dieci.

Questa cosa funziona identicamente se abbiamo solo due simboli. Possiamo rappresentare qualsiasi quantità, e il simbolo più a destra rappresenterà il numero di quantità uno (20, poi andando a sinistra avremo il numero di quantità due (21), poi il numero di quantità quattro (22) e via dicendo con le potenze di due.

Il numero di simboli usati, in matematica, si chiama base. Se hai dieci simboli stai lavorando in base dieci, se ne hai due stai lavorando in base due. In base due, quindi, il nostro caro 29 diventa 11101. Una volta sedici, più una volta otto, più una volta quattro, più zero volte due, più una volta uno. Piuttosto semplice, no?

Nulla ci impedisce di usare più di dieci simboli, tra l’altro. Con buona pace dei matematici più affezionati alle cifre, si sopperisce alla carenza di cifre aggiungendo le lettere: l’undicesimo simbolo è “a” e così via. Ad esempio, il nostro caro vecchio 29 diventa 1d in base sedici: una volta sedici e tredici volte uno.

Bit e byte

In quasi tutte le lingue, la singola cifra rappresentante una qualche informazione è stata chiamata “bit” (che in inglese suona grossomodo come “pezzettino”).

La maggior parte delle informazioni, però, non è rappresentabile con un singolo bit. Se ad esempio devo rappresentare il colore di un semaforo, ho bisogno di tre valori diversi, mentre di bit diversi ne posso fare soltanto due.

Per diversi motivi, si è a un certo punto della storia imposta come quantità “minima” (tra virgolette perché può essere ridotta se lo si fa con criterio) di informazione una sequenza di otto bit, chiamata quasi sempre (perdonatemi francofoni) byte. Trattando il byte come simbolo, quindi, si possono rappresentare 256 informazioni diverse. Lo si vede facilmente accorgendosi che il numero più grande rappresentabile è 11111111, che in base dieci è 255. È una buona soluzione: non sono così poche da renderlo insufficiente, ma non sono neanche così tante da renderlo spazio sprecato (perché il byte è sempre lungo otto bit, il nostro ormai amato 29, scritto come byte, è 00011101, con i suoi zeri a sinistra).

Se ho bisogno di rappresentare più di 256 cose diverse, posso tranquillamente utilizzare due byte. Se invece ne devo rappresentare molte meno, posso valutare se mi basti una frazione intera di byte: mezzo, o un quarto, o perché no un solo bit, a patto solitamente di fare qualcosa con il resto, che sia metterci altre informazioni di pari lunghezza o riempirlo di zeri. In un sistema di comunicazione o memorizzazione, il numero di byte necessari a contenere un simbolo di quel sistema viene detto “parola”, un po’ come se il byte fosse la “lettera”.

Rappresentare i byte

A volte, le informazioni memorizzate in una serie di byte sono informazioni che non hanno nessun’altra forma che non quella: sono i cosiddetti “dati binari”. Un esempio che abbiamo tutti molto chiaro sono gli indirizzi delle macchine collegate a una rete, i cosiddetti “indirizzi IP”.

Nello standard IP con cui abbiamo maggiore familiarità, la versione 4, l’indirizzo di una macchina è una sequenza di trentadue bit, e quindi otto byte. Ad esempio, l’indirizzo di una macchina vista dalla macchina stessa è la sequenza 0111111000000000000000000000001.

Anche se molto bella per i computer, una cosa del genere è una schifezza non da poco per noi umani.

Cerchiamo quindi, di solito, di tradurre le informazioni binarie che dobbiamo maneggiare anche noi in cose che ci sia più comodo gestire, ricordare, scrivere e dettare.

Una tecnica, quella che utilizziamo proprio per gli indirizzi IP, è quella di tradurre ogni singolo byte nel numero in base dieci corrispondente. 01111111 diventa quindi 127, i due 00000000 diventano 0, e il 00000001 finale diventa 1: l’indirizzo IP della macchina vista dalla macchina stessa, per noi, è 127.0.0.1.

Un altro modo, forse anche più diffuso perché più compatto, è utilizzare la base sedici. Due cifre in base sedici permettono di rappresentare, esattamente come otto cifre in base due, duecentocinquantasei simboli, da 0 a ff. Questo sistema è poco comune tra le cose di uso quotidiano, ma lo troviamo abbastanza spesso quando dobbiamo rappresentare dati binari (come ad esempio i colori) mentre scriviamo software. Quel nostro IP, comunque, finirebbe per essere 7f.0.0.1 o, se ci piacciono gli zeri, 7f0000001.

Le codifiche

Quando invece vogliamo rappresentare un’informazione che nel normale uso ha tutt’altra forma ci tocca associare a ogni aspetto di quell’informazione un simbolo fatto di byte. Questa operazione si chiama codifica.

Le cose più facili da codificare sono quelle che non sono infinite, ovviamente. Si conta quante sono, si decide quanti byte servono, si associa ogni cosa a una sequenza di bit tutta sua.

Una delle prime codifiche abbastanza universalmente accettate è stata proprio di questo genere qui: la prima cosa che viene in mente di dover codificare è il necessario per scrivere, l’alfabeto. E infatti si sono prese le lettere maiuscole e minuscole dell’alfabeto latino, le cifre, un po’ di segni di punteggiatura, lo spazio, una manciata di cose utili, si è visto che erano meno di 128 (numero per cui sono sufficienti sette bit), e si è associato un diverso byte ad ogni simbolo, tenendo sempre a zero il bit più a sinistra.

Impostando a 1 il bit a sinistra, così, si potevano avere a disposizione altri 128 simboli da usare in codifiche basate sull’ASCII ma che avessero in più le lettere proprie di gruppi di lingue (le accentate, le greche, le cirilliche..).

A volte anche le cose finite sono in numero enorme: se ad esempio si vogliono cominciare a codificare anche le lingue che hanno degli ideogrammi, i simboli diventano tantissimi, anche cercando di rappresentarli per parti. Per questo, esistono codifiche ormai estremamente complicate anche solo nell’ambito della scrittura.

Unicode, ad esempio, permette di avere parole di lunghezza variabile (da uno a quattro byte) in grado di associare una sequenza di bit a ogni carattere di ogni lingua esistente, morta, settoriale (matematica, musica) e inventata perché tanto c’era spazio (emoji). Finché sono finite, è sempre possibile.

I problemi iniziano a sorgere quando le cose sono infinite. Codificare insiemi infiniti di cose è ovviamente impossibile: non possiamo scegliere una codifica che ci permetta di memorizzare o trasmettere un numero qualunque, o un suono qualunque, o un colore qualunque.

Quello che si fa, quindi, è creare sempre un sottoinsieme finito di cose. Ad esempio, se voglio rappresentare un numero intero, dovrò scegliere il massimo e il minimo numero rappresentabile nella mia codifica. Se voglio rappresentare un numero tra 0 e 255 posso fare facilmente una codifica di un byte che associa ogni possibile byte a ogni numero. Se voglio rappresentare un numero qualsiasi tra zero e uno, dovrò anche scegliere “quante cifre dopo la virgola”, ovvero la precisione della mia codifica, per definire quanti siano i numeri tra zero e uno e regolarmi di conseguenza.

Le cose si complicano ulteriormente per cose che sono oggetti fisici, e che vogliamo riprodurre più fedelmente possibile Per codificare un suono, dobbiamo decidere quali frequenze codificare e quali saltare, approssimandole alle frequenze vicine o ricreandole combinando frequenze codificate, e possono essere tecniche abbastanza complesse (il cosiddetto campionamento). Se vogliamo codificare i colori, dobbiamo anche lì scegliere quali codificare e quali no, approssimandoli a colori simili o riproducendoli giustapponendo colori codificati.

Insomma, in molti casi le informazioni contenute in un computer sono un’approssimazione della realtà.

Sebbene questa cosa possa sembrare dannosa, non lo è poi molto: ci siamo abituati da sempre, poiché lo stesso problema lo abbiamo misurando le cose e facendone esperienza. Un tavolo lungo due metri e cinque centimetri magari è lungo due metri e cinquantadue millimetri, ma decidiamo che meno del centimetro non ci interessa e diciamo “cinque centimetri”. Una maglietta rossa è rossa, al massimo rosso scuro, decidiamo nella nostra testa di codificare un bel po’ di sfumature sotto quello stesso nome.

L’importante è scegliere sempre la codifica adatta.

La memorizzazione

Le tecniche per memorizzare sequenze di “zero” e “uno”, nel tempo, sono state moltissime. Proviamo a ridurle a un po’ di gruppi coerenti, così da farci un po’ un’idea dell’evoluzione.

Una delle tecniche più antiche, è quella di stampare su un mezzo fisico una sequenza di “presenza” e “vuoto”. La più antica memoria di questo tipo sono probabilmente le schede perforate, delle tessere di cartoncino con una sequenza di fori, che le macchine possono leggere distinguendo tra “c’è un foro” e “c’è del cartoncino”. Il loro utilizzo è iniziato ben prima dei computer, ad esempio sui telai automatici, e hanno avuto una vita non brevissima anche con l’avvento dei computer. Appartengono a questa categoria di memorie anche i CD e i dischi ottici in generale (che usano sequenze di “riflette la luce” e “non riflette la luce”), i codici a barre (“bianco”, “nero”), e le memorie ROM (“passa corrente”, “non passa corrente”).

Un’altra tecnica non particolarmente molto recente ma ancora assai utilizzata, è quella di memorizzare le informazioni come sequenze di poli magnetici, dove “positivo” e “negativo” sono i due simboli. Le memorie più antiche di questo genere sono probabilmente quelle a nucleo magnetico, che utilizzavano pareti di anellini magnetici di ceramica posti sugli incroci di una griglia di fili elettrici che ne permettevano la polarizzazione. Sono memorie di questo tipo anche i nastri magnetici e i floppy disk, che hanno avuto lunghe fortune, e gli hard disk magnetici ancora oggi in uso, su cui una testina legge e scrive informazioni cambiando la polarità a porzioni di superficie.

Un’ultima tecnica consiste nel memorizzare dati come stati di componenti elettroniche, solitamente oggetti che sono concettualmente coppie transistor/condensatori ma che ai fatti sono transistor e basta, grazie alle proprietà fisiche dei semiconduttori che li compongono. Sono memorie di questo tipo sia le RAM, sia le varie memorie a stato solido (hard disk a stato solido, chiavette USB…)

La trasmissione

La trasmissione di informazioni codificate come dati binari viene effettuata in moltissimi modi diversi, dipendenti sia dal mezzo fisico (cavi, radiofrequenza), sia dal tipo di informazione.

Solitamente, le informazioni vengono trasmesse (ed elaborate) sotto forma di impulsi elettrici (o l’equivalente nel mezzo trasmissivo) di durata predefinita (il ciclo di clock) e differenza di potenziale nota. Un byte 00101010, ad esempio, può essere rappresentato come l’assenza di corrente per due cicli di clock, seguita poi da tre coppie di cicli di clock in cui per un ciclo c’è corrente e per un ciclo no. Esistono tecniche per separare sia i bit tra loro, sia i byte, senza doversi per forza assicurare che il clock di trasmissione e quello di ricezione siano costantemente sincronizzati.

A seconda della struttura del mezzo e dei componenti, la trasmissione può avvenire sia in serie, ovvero come sequenza su un filo (o sul suo equivalente nel mezzo), sia in parallelo, ovvero trasmettendo un’intera parola tutta assieme su un numero sufficiente di fili. Entrambe le soluzioni hanno vantaggi e svantaggi: la trasmissione seriale è ovviamente più lenta (per trasmettere una parola le servono tanti cicli quanto è lunga la parola), ma richiede un singolo “filo”, ed è quindi meno ingombrante e meno difficile da gestire.

La trasmissione parallela richiede un certo ingombro, ma permette di trasferire un’intera parola ad ogni ciclo di clock. La trasmissione parallela è anche un po’ più sensibile agli errori di quella seriale (un errore di trasmissione tende a far “buttare” un’intera parola e non un singolo bit). Questo ha fatto sì che con l’aumento della capacità di gestire cicli di clock molto brevi siano andate diminuendo le interfacce parallele in favore di quelle seriali: le interfacce esterne parallele (come la porta parallela un tempo molto comune per le stampanti) sono state del tutto sostituite dall’USB, seriale, e le interfacce parallele per il collegamento di dischi (IDE) sono state sostituite da interfacce seriali come SATA.

Conclusioni

Qui ci sarebbe stato bene anche qualche paragrafo su come vengono elaborate le informazioni codificate a questo modo, ma è un argomento troppo vasto e complesso per un articolo di questo tipo, purtroppo.

Come al solito ho cercato di rendere le cose un po’ più semplici di quello che sono, per dare un’idea generale. Stavolta le ho rese un bel po’ più semplici, spero non sia troppo. Alla prossima!