Come funziona il complemento a due
I computer non hanno il segno meno, solo bit. Il complemento a due è la convenzione che quasi ogni CPU usa per memorizzare interi negativi: il bit più alto (il bit di segno) conta come 2ⁿ⁻¹ negativo invece che positivo. A 8 bit, il bit più alto vale −128, quindi 1111 1111 = −128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = −1, e 1000 0000 è −128.
Gli stessi bit letti senza segno sono 255 e 128. Se un valore è «davvero» negativo lo decide il tipo nel tuo codice (int8_t contro uint8_t), non i bit. Per questo questo strumento mostra sempre entrambe le letture.
Negare un numero a mano
- Scrivi il valore positivo in binario alla larghezza piena (42 =
0010 1010). - Inverti ogni bit (
1101 0101). - Aggiungi 1 (
1101 0110). Questo è −42, cioè 0xD6 o 214 senza segno.
Il trucco funziona in entrambe le direzioni: applicato a −42 restituisce 42. Lo strumento qui sopra mostra questi passaggi per qualsiasi input.
Perché è progettato così
Con il complemento a due l’hardware non ha bisogno di un sottrattore separato. Sommare un numero negativo è una normale addizione binaria in cui si scarta il riporto dal bit più alto: 42 + (−42) = 0010 1010 + 1101 0110 = 1 0000 0000, si scarta il nono bit, risultato 0. Inoltre c’è un solo zero, a differenza del segno-modulo o del complemento a uno.
Intervalli di valori
| Larghezza | Con segno | Senza segno | Tipo tipico |
|---|---|---|---|
| 8 | −128 … 127 | 0 … 255 | int8_t, byte |
| 16 | −32.768 … 32.767 | 0 … 65.535 | int16_t, short |
| 32 | −2.147.483.648 … 2.147.483.647 | 0 … 4.294.967.295 | int32_t, int |
| 64 | −9,22 × 10¹⁸ … 9,22 × 10¹⁸ | 0 … 1,84 × 10¹⁹ | int64_t, long |
L’intervallo negativo è più grande di uno rispetto a quello positivo perché lo zero occupa uno degli schemi «positivi». Per questo -INT_MIN va in overflow: 128 non sta in un byte con segno.
Domande
Quanto fa −1 in complemento a due?
Tutti uno: 0xFF a 8 bit, 0xFFFF a 16, 0xFFFFFFFF a 32. Invertendo 0000 0001 si ottiene 1111 1110, aggiungendo 1 si ottiene 1111 1111.
Come capisco se un numero binario è negativo?
Dal bit più a sinistra nella larghezza dichiarata. Se è 1 e il valore ha il segno, il numero è negativo. 1000 0000 è −128 come int8 ma 128 come uint8; i bit da soli non lo decidono.
Perché 127 + 1 dà −128?
Overflow con segno. 0111 1111 + 1 = 1000 0000, che imposta il bit di segno. In C è comportamento indefinito; nella maggior parte degli altri linguaggi il valore gira a −128.
Cos’è l’estensione del segno?
Allargare un valore con segno copiando il bit di segno nei nuovi bit alti, così −1 (0xFF) diventa 0xFFFF, non 0x00FF. Un cast da int8 a int16 lo fa automaticamente; un cast di un valore senza segno riempie invece con zeri. Cambia la larghezza qui sopra con un numero negativo inserito per vederlo.
Il bit di segno è solo un flag?
No. In complemento a due il bit di segno ha un valore (−2ⁿ⁻¹) e partecipa all’aritmetica. Questo lo distingue dal segno-modulo, dove il bit alto segna solo il segno, e dalla virgola mobile, dove il segno è un flag a parte.