Vai al contenuto
Topos Uranos

Riepilogo

Un intero del programma non è un intero della matematica: è una successione finita di bit, e con nn bit si possono rappresentare esattamente 2n2^n valori distinti. Questa lezione studia tale rappresentazione e le sue conseguenze. Insegna a scrivere un intero in base 2 e in base 16 e a verificarlo con le specifiche di formato {:b} e {:x}; presenta i tipi interi di C++, la loro dimensione, che si misura con sizeof, e il loro intervallo, che si consulta con std::numeric_limits; spiega come si rappresentano i negativi mediante il complemento a due e perché l’intervallo non è simmetrico; distingue gli interi con segno dagli interi senza segno, e studia ciò che accade quando un’operazione esce dall’intervallo: per quelli senza segno, lo standard definisce il risultato, che è l’aritmetica modulo 2n2^n; per quelli con segno, non lo definisce, e il programma resta fuori da ogni garanzia. Si chiude con un criterio per scegliere il tipo di ciascun dato.

Obiettivi di apprendimento

  1. Convertire interi fra le basi 10, 2 e 16, e verificare la conversione con le specifiche di formato {:b} e {:x}.
  2. Calcolare l’intervallo di un intero di nn bit con segno e senza segno, e verificarlo con std::numeric_limits e sizeof.
  3. Spiegare il complemento a due e scrivere la rappresentazione su otto bit di un piccolo numero negativo.
  4. Distinguere l’overflow con segno, che è comportamento indefinito, dall’overflow senza segno, che è aritmetica modulo 2n2^n, e prevedere il risultato del secondo.
  5. Scegliere il tipo intero adatto a un dato secondo il suo intervallo prevedibile, usando i tipi a larghezza fissa di <cstdint> quando la larghezza conta.

Sistemi posizionali

La lezione Variabili e memoria ha presentato la variabile come una cella con nome, tipo e valore. Ora, la memoria non conserva cifre decimali: conserva bit, ciascuno dei quali vale 0 o 1, raggruppati in byte di otto bit (lo standard ne esige almeno otto; tutti i sistemi attuali ne usano esattamente otto). Per sapere quali valori può assumere una variabile intera bisogna dunque sapere come si scrive un numero con due sole cifre, ed è ciò che fa un sistema posizionale in base 2.

In un sistema posizionale in base bb, ogni cifra vale secondo la posizione che occupa: l’ultima moltiplica b0=1b^0 = 1, la precedente b1b^1, quella ancora precedente b2b^2, e così via. In base 10, 20262026 significa 2⋅103+0⋅102+2⋅101+6⋅1002 \cdot 10^3 + 0 \cdot 10^2 + 2 \cdot 10^1 + 6 \cdot 10^0. In base 2 le cifre sono 0 e 1, e i pesi sono le potenze di 2: 1,2,4,8,16,32,64,128,…1, 2, 4, 8, 16, 32, 64, 128, \ldots Ne segue la regola per passare dal binario al decimale: si sommano i pesi delle posizioni in cui c’è un 1.

EsempioDal binario al decimale

La figura legge la configurazione di otto bit 00101101. Sopra ogni cella c’è il suo peso; gli uno si accendono uno alla volta, da sinistra a destra, e il valore accumula i loro pesi.

Dimostrazione

  1. Otto celle, tutte spente: il valore è 0. I pesi, da destra a sinistra, sono 1,2,4,…,1281, 2, 4, \ldots, 128.

  2. Il primo uno è nella posizione di peso 32: il valore accumulato è 32.

  3. Il successivo, in quella di peso 8: 32+8=4032 + 8 = 40.

  4. Il successivo, in quella di peso 4: 40+4=4440 + 4 = 44.

  5. L’ultimo, in quella di peso 1: 44+1=4544 + 1 = 45. La configurazione 00101101 rappresenta il numero 45.

  6. Raggruppati a quattro a quattro, i bit danno le cifre esadecimali: 0010 è 2 e 1101 è 13, che in base 16 si scrive d. Dunque 45 si scrive 2d in base 16.

Il cammino inverso, dal decimale al binario, si percorre per divisioni successive: si divide il numero per 2, si annota il resto (0 o 1) e si ripete con il quoziente fino ad arrivare a 0; i resti, letti dall’ultimo al primo, sono le cifre binarie. Per 45: 45=2⋅22+145 = 2 \cdot 22 + 1, 22=2⋅11+022 = 2 \cdot 11 + 0, 11=2⋅5+111 = 2 \cdot 5 + 1, 5=2⋅2+15 = 2 \cdot 2 + 1, 2=2⋅1+02 = 2 \cdot 1 + 0, 1=2⋅0+11 = 2 \cdot 0 + 1; letti a ritroso, i resti danno 101101, che con due zeri a sinistra è la configurazione della figura. La ragione è che ogni resto è la cifra delle unità del numero che si divide, e ogni quoziente è quel numero spostato di una posizione verso destra. Lo stesso metodo vale per qualsiasi base: per la base 16 si divide per 16, e i resti da 10 a 15 si scrivono con le lettere da a a f.

La base 16, o esadecimale, esiste per comodità: poiché 16=2416 = 2^4, ogni cifra esadecimale corrisponde esattamente a quattro bit, e un byte si scrive con due cifre. Un numero di 32 bit occupa trentadue cifre in binario, che si leggono male, e solo otto in esadecimale, che si convertono in binario senza calcoli, cifra per cifra. Per questo i programmatori leggono la memoria in esadecimale. Il fondamento matematico dei sistemi posizionali si studia nel corso Algebra e Geometria I, nella lezione «Sistemi di numerazione posizionale».

C++ ammette interi scritti direttamente in queste basi: un letterale che comincia con 0b è in binario, e uno che comincia con 0x, in esadecimale. All’interno di qualsiasi letterale si può mettere un apostrofo come separatore di cifre, che il compilatore ignora e il lettore apprezza: 0b0010'1101, 1'000'000. Nell’output, la specifica di formato {:b} scrive un intero in binario, {:x} in esadecimale con le minuscole e {:X} con le maiuscole; il segno # antepone il prefisso, e una larghezza preceduta da uno zero riempie con zeri a sinistra.

EsempioUno stesso numero, tre scritture
Programma in C++
#include <print>​int main(){    const int decimal{45};    const int binary{0b0010'1101};    const int hexadecimal{0x2d};    std::println("{} {} {}", decimal, binary, hexadecimal);    std::println("binario: {:b}", decimal);    std::println("otto bit: {:08b}", decimal);    std::println("esadecimale: {:x} {:X} {:#x}", decimal, decimal, decimal);}
Output
45 45 45binario: 101101otto bit: 00101101esadecimale: 2d 2D 0x2d

Le tre variabili conservano lo stesso valore, scritto in tre basi, e la prima riga dell’output lo mostra: la base è una proprietà della scrittura, non del numero. La memoria conserva nei tre casi la stessa configurazione di bit. La specifica {:b} omette gli zeri a sinistra; {:08b} completa otto cifre.