Esistono diversi modi per rappresentare numeri in virgola fissa, ma il sistema più utilizzato è quello in modulo/segno (analogamente alla conversione in virgola mobile). Tuttavia, talvolta si usa anche la conversione in complemento a 2.
Conversione in formato modulo/segno
Si procede come segue:
si calcola il valore assoluto del numero
si converte la parte intera eseguendo delle divisioni per due fino a quando il quoziente è 0; i resti, letti dal baso verso l'alto, rappresentano la conversione
si converte la parte frazionaria eseguendo delle moltiplicazioni per due; le unità, lette dall'alto verso il basso, rappresentano la conversione
si ricompone il numero, allineandolo nella maschera di bit a disposizione
se il numero era negativo, si sostituisce il primo bit (quello più a sinistra) con 1, altrimenti si lascia a 0
Negli esempi che seguono supponiamo 1 bit segno, 8 bit parte intera, 7 bit parte frazionaria.
Esempio: -14,25
Escludo temporaneamente il segno e converto prima la parte intera:
1410 = 11102
14:2=7 R=0 0
7:2=3 R=1 1
3:2=1 R=1 2
1:2=0 R=1 3
e poi la parte frazionaria:
0,2510=0,012 (riempio il resto della parte frazionaria con 0).
0,25*2=0,5 U=0 -1
0,5 *2=1,0 U=1 -2
0 (termino conversione perché ho incontrato 0)
Risultato(valore assoluto): 14,2510 = 1110,012
Infine estendo il numero (al numero di bit effettivo) e cambio il primo bit (segno):
Risultato(modulo/segno): -14,2510 = 10001110,012
Esempio: -123,21
Escludo temporaneamente il segno e converto prima la parte intera:
12310 = 11110112
e poi la parte frazionaria:
0,2110=0,0011012 (approssimato).
Risultato(valore assoluto): 123,2110 = 1111011,0011012
Infine estendo il numero (al numero di bit effettivo) e cambio il primo bit (segno):
Risultato(modulo/segno): -123,2110 = 11111011,0011012
Conversione in formato complemento a 2
Come per la conversione precedente, per questa conversione si procede convertendo il valore assoluto del numero (sia parte intera che parte frazionaria).
Fatto questo però, si esegue il NOT del numero e si somma 1 al bit meno significativo (che vale 1) della parte frazionaria, gestendo gli eventuali riporti.
Negli esempi che seguono supponiamo 1 bit segno, 8 bit parte intera, 7 bit parte frazionaria.
Esempio: -14,25
Escludo temporaneamente il segno e converto prima la parte intera:
1410 = 11102
14:2=7 R=0 0
7:2=3 R=1 1
3:2=1 R=1 2
1:2=0 R=1 3
e poi la parte frazionaria:
0,2510=0,012 (riempio il resto della parte frazionaria con 0).
0,25*2=0,5 U=0 -1
0,5 *2=1,0 U=1 -2
0 (termino conversione perché ho incontrato 0)
Risultato(valore assoluto): 14,2510 = 1110,012 Infine estendo il numero (al numero di bit effettivo), ne eseguo il NOT e aggiungo 1 al bit meno significativo:
Risultato(complemento a 2): -14,2510 = 11110001,102 + 00000000,012 = 11110001,112 La verifica che i due numeri opposti hanno somma 0 viene lasciata al lettore:
00001110,012 + 11110001,112 = ?
Esempio: -123,21
Escludo temporaneamente il segno e converto prima la parte intera:
12310 = 11110112
e poi la parte frazionaria:
0,2110=0,0011012 (approssimato).
Risultato(valore assoluto): 123,2110 = 1111011,0011012 Infine estendo il numero (al numero di bit effettivo), ne eseguo il NOT e aggiungo 1 al bit meno significativo:
a) Risultato(complemento a 2): -123,2110 = 10000100,1100102 + 00000000,0000012 = 10000100,1100112
b) Risultato(complemento a 2): -123,2110 = (-(12310)) + (-(0,2110)) = 100001012 + 11111111,1100112 = 10000100,1100112
Il problema della precisione finita. Numeri aperiodici, periodici con e senza antiperiodo
Supponiamo di voler convertire alcuni numeri in virgola fissa (formato modulo/segno), avendo a disposizione un totale di 24 bit, di cui: 1 bit di segno, 7 cifre di parte intera e 16 cifre di parte frazionaria.
Notare che 23 bit di mantissa sono gli stessi presenti in IEEE-754 (singola precisione)
Esempio: -14,25 (numero aperiodico)
Escludo temporaneamente il segno e converto prima la parte intera:
1410 = 11102
14:2=7 R=0 0
7:2=3 R=1 1
3:2=1 R=1 2
1:2=0 R=1 3
e poi la parte frazionaria:
0,2510=0,012 (riempio il resto della parte frazionaria con 0).
0,25*2=0,5 U=0 -1
0,5 *2=1,0 U=1 -2
0 (termino conversione perché ho incontrato 0)
Risultato(valore assoluto): 1110,012
Esempio: -14,2 (numero con periodo)
Escludo temporaneamente il segno e converto prima la parte intera:
1410 = 11102
14:2=7 R=0 0
7:2=3 R=1 1
3:2=1 R=1 2
1:2=0 R=1 3
e poi la parte frazionaria:
0,210=0,00112 (tutte le cifre dopo la virgola costituiscono il periodo).
0,2 *2=0,4 U=0 -1
0,4 *2=0,8 U=0 -2
0,8 *2=1,6 U=1 -3
0,6 *2=1,2 U=1 -4
0,2 (termino conversione perché ho già incontrato questo numero)
Risultato(valore assoluto): 1110,00112
Esempio: -14,3 (numero con periodo e antiperiodo)
Escludo temporaneamente il segno e converto prima la parte intera:
1410 = 11102
14:2=7 R=0 0
7:2=3 R=1 1
3:2=1 R=1 2
1:2=0 R=1 3
e poi la parte frazionaria:
0,310 = 0,010012 (il primo zero dopo la virgola costituisce l'antiperiodo, le cifre rimanenti costituiscono il periodo).
0,3 *2=0,6 U=0 -1
0,6 *2=1,2 U=1 -2
0,2 *2=0,4 U=0 -3
0,4 *2=0,8 U=0 -4
0,8 *2=1,6 U=1 -5
0,6 (termino conversione perché ho già incontrato questo numero)
Risultato(valore assoluto): 1110,010012
Esempio: -14,33 (numero aperiodico entro la mantissa)
Escludo temporaneamente il segno e converto prima la parte intera:
1410 = 11102
14:2=7 R=0 0
7:2=3 R=1 1
3:2=1 R=1 2
1:2=0 R=1 3
e poi la parte frazionaria:
0,3310 = 0,01010100011100002.
0,33*2=0,66 U=0 -1
0,66*2=1,32 U=1 -2
0,32*2=0,64 U=0 -3
0,64*2=1,28 U=1 -4
0,28*2=0,56 U=0 -5
0,56*2=1,12 U=1 -6
0,12*2=0,24 U=0 -7
0,24*2=0,48 U=0 -8
0,48*2=0,96 U=0 -9
0,96*2=1,92 U=1 -10
0,92*2=1,84 U=1 -11
0,84*2=1,68 U=1 -12
0,68*2=0,36 U=0 -13
0,36*2=0,72 U=0 -14
0,72*2=1,44 U=0 -15
0,44*2=0,88 U=0 -16
0,88 ... (termino conversione perché sono finiti i bit disponibili per la parte frazionaria)
Risultato(valore assoluto): 1110,01010100011100002
Notiamo che se avessimo avuto a disposizione qualche ulteriore bit per la parte frazionaria, avremmo notato che il numero è periodico (con antiperiodo). La parte frazionaria sarebbe risultata: 0,3310 = 0,01010100011100001110002. Infatti:
0,88*2=1,76 U=1 -17
0,76*2=1,52 U=1 -18
0,52*2=1,04 U=1 -19
0,04*2=0,08 U=0 -20
0,08*2=0,16 U=0 -21
0,16*2=0,32 U=0 -22
0,32 (termino conversione perché ho già incontrato questo numero)
Conversione da decimale in virgola mobile (in formato P754)
Esistono diversi modi per rappresentare numeri in virgola mobile, ma il sistema più utilizzato è lo standard IEEE-P754; questo metodo comporta l'utilizzo della notazione scientifica, in cui ogni numero è identificato dal segno, da una mantissa (1,xxxxx) e dall'esponente (nyyyyy).
La procedura standard per la conversione da numero decimale a numero binario P754 è la seguente:
Prima di tutto il numero, in valore assoluto, va convertito in binario.
Il numero va poi diviso (o moltiplicato) per 2 fino a ottenere una forma del tipo 1,xxxxxx (normalizzazione).
Di questo numero viene eliminato l'1 iniziale (per risparmiare memoria) [hidden bit]
Il numero di volte per cui il numero è stato diviso (o moltiplicato, e nel qual caso il valore sarà negativo) per 2 rappresenta l'esponente: questo valore (decimale) va espresso in eccesso h, ovvero è necessario sommare h e convertire il numero risultante in binario. Il valore di h dipende dalla rappresentazione:
h=15, nel caso di rappresentazione in half-precision
h=127, nel caso di rappresentazione in single-precision
h=1023, nel caso di rappresentazione in double-precision
A questo punto ho raccolto tutti i dati necessari per memorizzare il numero: in base al numero di bit che ho a disposizione possiamo utilizzare vari formati:
Half-precision format (16 bits): 1 bit per il segno, 5 bit per l'esponente (bias 15) e 10 bit per la mantissa;
Single-precision format (32 bits): 1 bit per il segno, 8 bit per l'esponente (bias 127) e 23 bit per la mantissa;
Double-precision format (64 bits): 1 bit per il segno, 11 bit per l'esponente (bias 1023) e 52 per la mantissa;
Quad-precision format (128 bits): 1 bit per il segno, 15 bit per l'esponente (bias 16383) e 112 per la mantissa.
Come si può vedere, se k è il numero di bit occupati dall'esponente, il bias vale 2k-1-1. Ricordo che il valore 2k-1 (tutti i bit a 1) è riservato per scopi specifici (NaN, Inf, etc).
Esempio1
Convertire in floating point IEEE-754 il numero -14,312510.
Escludo temporaneamente il segno e converto (in binario) prima la parte intera:
1410 = 11102
14:2=7 R=0 0
7:2=3 R=1 1
3:2=1 R=1 2
1:2=0 R=1 3
e poi converto (in binario) la parte frazionaria:
0,312510 = 0,01012.
0,3125*2=0,625 U=0
0,625 *2=1,25 U=1
0,25 *2=0,5 U=0
0,5 *2=1 U=1
0 (termino conversione perché ho incontrato 0)
Risultato(valore assoluto): 1110,01012
Ora normalizzo il numero, spostando la virgola di 3 posizioni verso sinistra: 1110,01012 = 1,11001012 * 23.
Una volta eliminato l'hidden bit, la mantissa diventa quindi 1100101, mentre l'esponente è 3.
Single-precision
Completo la mantissa con zeri fino ad arrivare ai 16 bit a mia disposizione.
Data una frazione, eseguendo la divisione tra numeratore e denominatore si ottiene un numero decimale. Ancor prima di eseguire la divisione è però possibile stabilire che tipo di numero decimale genera la frazione.
Prima si riduce la frazione ai minimi termini, poi si calcola la scomposizione in fattori primi del denominatore. A questo punto:
se non ci sono fattori primi (il denominatore è 1) → numero intero (frazione apparente);
Esempio: 18/9 = 2/1. fattori primi denominatore: nessuno → 2
se tra i fattori primi appaiono solo 2 e 5 → numero decimale limitato;
Esempio: 12/32 = 3/8. fattori primi denominatore: 2 → 0,375
se tra i fattori primi non appaiono né il 2 né il 5 → numero decimale illimitato periodico semplice;
Esempio: 12/18 = 2/3. fattori primi denominatore: 3 → 0,6
se tra i fattori primi appaiono altri numeri primi oltre al 2 o al 5 → numero decimale illimitato periodico misto.
Esempio: 2/12 = 1/6. fattori primi denominatore: 2 e 3 → 0,16
Come cambiano le regole se la base di destinazione non è 10 (ad esempio 2)?
Calcolo la frazione generatrice del numero in base 10, poi ricerco i fattori primi del denominatore.
se non ci sono fattori primi (il denominatore è 1), ovvero se il numero in base 10 è intero → numero binario intero;
Esempio: 13 = 11012
se fra i fattori c'è solo 2 → numero binario limitato;
Esempio: 0,25 = 25/100 = 1/4 = 0,012
se fra i fattori primi non c'è il 2 → numero binario illimitato periodico semplice;
Esempio: 0,2 = 20/100 = 1/5 = 0,00112
se fra i fattori primi ci sono sia il 2 che altri fattori → numero binario illimitato periodico misto;
Esempio: 0,3 = 30/100 = 3/10 = 0,010012
Non otterrò mai un numero binario illimitato non periodico, vista la precisione finita del numero in input.
// @brief Algoritmo (salvo errori)
// @author Roberto Foschini
// @date 31/01/2022
// @param $num di una frazione generatrice
// @param $den di una frazione generatrice
// semplifico la frazione
$num /= gcd($num, $den);
$den /= gcd($num, $den)
// $lap = lunghezza antiperiodo (dipende solo da $den)
for ($lap=0; $den%2==0; ++$lap)
$den/=2;
// $lp = lunghezza periodo (dipende solo da $den)
for ($r=1, $lp=0; $r>1 || $lp==0; ++$lp)
$r = ($r*2) % $den;
// determino la tipologia di numero binario
if($den==1)
return $lap; // binario limitato con $lap cifre binarie
else {
if($lap==0)
return $lp; // binario illimitato periodico con $lp cifre binarie
else
return $lap, $lp; // binario illimitato misto con $lap|$lp cifre binarie
}