Riepilogo
Un intero del programma non è un intero della matematica: è una successione finita di bit, e con bit si possono rappresentare esattamente 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 ; 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
- Convertire interi fra le basi 10, 2 e 16, e verificare la conversione con le specifiche di formato
{:b}e{:x}. - Calcolare l’intervallo di un intero di bit con segno e senza segno, e verificarlo con
std::numeric_limitsesizeof. - Spiegare il complemento a due e scrivere la rappresentazione su otto bit di un piccolo numero negativo.
- Distinguere l’overflow con segno, che è comportamento indefinito, dall’overflow senza segno, che è aritmetica modulo , e prevedere il risultato del secondo.
- 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 , ogni cifra vale secondo la posizione che occupa: l’ultima moltiplica , la precedente , quella ancora precedente , e così via. In base 10, significa . In base 2 le cifre sono 0 e 1, e i pesi sono le potenze di 2: Ne segue la regola per passare dal binario al decimale: si sommano i pesi delle posizioni in cui c’è un 1.
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
Otto celle, tutte spente: il valore è 0. I pesi, da destra a sinistra, sono .
Il primo uno è nella posizione di peso 32: il valore accumulato è 32.
Il successivo, in quella di peso 8: .
Il successivo, in quella di peso 4: .
L’ultimo, in quella di peso 1: . La configurazione
00101101rappresenta il numero 45.Raggruppati a quattro a quattro, i bit danno le cifre esadecimali:
0010è 2 e1101è 13, che in base 16 si scrived. Dunque 45 si scrive2din 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: , , , , , ; 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é , 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.
#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);}45 45 45binario: 101101otto bit: 00101101esadecimale: 2d 2D 0x2dLe 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.
Caricamento del contenuto…