Quando ho iniziato a scrivere questo articolo, avrei voluto intitolarlo "Il formato FP-4∞" oppure "FP-48 e succede un quarantotto!". Tuttavia, per l'ennesima volta, mi sono presto convinto che non c'è modo di realizzare un formato standard Floating-Point a 48 bit decente, che fornisca una precisione ottimale e un esponente abbastanza esteso; inevitabilmente, quindi, la scelta è dovuta ricadere sui soliti 52 bit, una parola di lunghezza "scomoda" da gestire ma davvero perfetta per ogni esigenza o quasi.
Detto questo, qui ho cercato di spingere all'estremo le idee presentate già in passato con il formato "52 bimodale", inventando un sistema "multi-modale" che consente di raggiungere valori di esponente davvero elevati, anche se con una precisione ridotta. Il sistema codifica inoltre numeri interi a 40 bit e numeri razionali "floating slash". Per illustrare in maniera comprensibile tutti questi artifici, conviene partire da un formato "classico" in virgola mobile, con un numero fisso di bit assegnati a esponente e mantissa, per poi aggiungere progressivamente queste varianti che lo potenziano.
Il floating point classico
Questo è lo standard di base, già ampiamente illustrato nel primo articolo di questa serie. Nel caso del formato a 52 bit, esso prevede un bit per il segno, 10 bit fissi per l'esponente e i rimanenti 41 per la mantissa. Con questa ripartizione, facendo ricorso al solito "trucco dell'hidden bit" (bit più significativo della mantissa nascosto o sottinteso, pari sempre a 1), è possibile ottenere una precisione garantita di (41+1)·Log(2)=12,64 cifre decimali, ovvero 12 cifre significative con ampio margine per gli arrotondamenti, che saranno comunque sempre inferiori a 10-0.,64=23% della cifra decimale meno significativa. L'esponente, se si utilizza il sistema classico e standardizzato, andrà da un valore minimo pari a (-29+2)=-510 a +(29-1)= +511 in formato binario, il che corrisponde a un range di circa ±154 in formato decimale; più precisamente, il numero più piccolo in valore assoluto risulterà ∼2,98·10-154 mentre il più grande sarà ∼1,34·10154. Il formato classico prevede che, quando l'esponente binario è formato da una sequenza di zeri, esso non rappresenti un vero esponente ma sta ad indicare che siamo in presenza di numeri "de-normalizzati", ovvero a virgola fissa e di entità sempre più piccola, in questo caso compresi tra ∼2,98·10-154 e zero. Invece, quando l'esponente è una sequenza di 1, esso sta a significare che siamo in presenza di un infinito oppure di un codice di errore NaN (not a number). Come già fatto per altri formati di mia invenzione, i numeri de-normalizzati non verranno affatto utilizzati perchè privi di senso in caso di esponente negativo aumentato, mentre per l'altro estremo, il numero di possibili combinazioni NaN viene ridimensionato e la sua codifica viene "annidata" insieme ad altre codifiche numeriche alternative.
L'esponente multi-modale
Rispetto al sistema canonico appena descritto, un piccolo sacrificio sul valore massimo dell'esponente può portare grossi vantaggi. Come mostrato nell'immagine di apertura, infatti, se ci limitiamo ad un esponente nell'intervallo ±146 invece di ±154, si rendono disponibili 52 valori binari dell'esponente, che vengono utilizzati per creare 3 diverse regioni con una parola di esponente ampliata e una precisione ridotta progressivamente. Si parte dal livello 1, con 46 combinazioni (23 positive e 23 negative) in cui 7 bit vengono sottratti dalla mantissa per generare 128 possibili valori di esponente per ciascuna combinazione; alla fine, quindi, ci ritroviamo 5888 valori binari corrispondenti ad un esponente esteso nell'intervallo ±886, sebbene con una mantissa che si accorcia di 2,1 cifre decimali. La scelta del punto esatto in cui far partire questo "livello 1" è un pò arbitraria e dettata dal buon senso.
Si passa poi al livello 2, dove 4 combinazioni (due positive e due negative) vengono moltiplicate per i 16384 valori binari che si generano da 14 bit sottratti alla mantissa. A fronte di ulteriori 2,1 cifre decimali sacrificate sulla precisione, stavolta ci ritroviamo con un range decimale ±9864. L'ultimo livello riguarda i due valori estremi di esponente binario originale, moltiplicati però per 2,1 milioni di combinazioni che si generano con ciascun segno; a questo punto, la precisione della mantissa si è più che dimezzata rispetto al valore iniziale ma l'esponente decimale è oltre 4000 volte più grande di quello consentito dal formato classico.
In alternativa allo schema presentato nella figura di apertura, naturalmente, esistono molte altre possibilità. Nella tabella seguente, ad esempio, ho incrementato i valori massimi di esponente nei vari livelli estesi, con un sacrificio minimo sull'esponente ottenibile nella zona di codifica FP classica (esponente massimo pari a 145 anziché 146). Stavolta la porzione inferiore con numeri molto piccoli non è riportata.
E' sempre importante sottolineare che, ad ogni passaggio di livello, si riparte da un valore nullo di esponente, una ridondanza solo apparente che vuole evitare di mescolare tra loro i diversi livelli di precisione, mantenendo traccia dell'eventuale, improbabile utilizzo nei calcoli di un numero piccolissimo o grandissimo con un ridotto numero di cifre significative. E' inoltre auspicabile che la modalità di esponente multi-modale sia un'opzione disattivabile, per chi dovesse avere bisogno di calcoli sempre precisi e non abbia bisogno di maneggiare numeri con esponenti estremi; in questi casi, si potrebbe attivare la codifica Floating classica o, più semplicemente, sostituire con i valori 0 e ±∞ i risultati a precisione ridotta.
Numeri interi, razionali e NaN
Quando i 10 bit di esponente assumono il valore massimo (una sequenza di 1 nel formato binario), questo serve a segnalare, per la parte restante della parola, un utilizzo diverso dal "Floating Point"; in quest'ultima, il primo bit è destinato a specificare quale sia questo utilizzo sui rimanenti 41 bit, scegliendo quindi tra due diverse situazioni e una sotto-situazione:
- Numero intero "signed", di valore compreso tra -240+1 e +240 ≈ 1,10·1012 (l'asimmetria trai i valori assoluti dei due estremi deriva, naturalmente, dalla necessità di codificare anche il numero zero).
- Numero razionale "signed" con linea di frazione mobile ("floating slash"). Escluso il bit destinato a codificare il segno, i rimanenti 39 bit sono divisi tra 3 bit che esprimono la posizione della linea di frazione e i rimanenti 36 bit ad esprimere numeratore e denominatore, ripartiti come indicato nello schema seguente, che consente fino a 33 bit di numeratore e denominatore (ma non contemporaneamente). Naturalmente, c'è il solito problema della fattorizzazione, per cui sarebbe opportuno eliminare le frazioni ridondanti tra loro, in caso di fattori primi comuni tra numeratore e denominatore; tuttavia, alla fine, si tratterebbe di un algoritmo complesso e di scarsa utilità pratica nella maggior parte dei casi.
- ±∞ e NaN sono in realtà un caso particolare del precedente, quando il "floating slash" assume il valore massimo pari a 7; anche qui, si hanno a disposizione 36 bit per rappresentare quasi 69 miliardi di combinazioni, il che dovrebbe essere più che sufficiente ad esprimere un codice di errore o di segnalazione...
In alternativa, si potrebbero impiegare due bit per quattro opzioni, le prime due dedicate a interi positivi e negativi, la terza ai razionali e l'ultima ai NaN.
Inizialmente, ero fortemente tentato di utilizzare all'interno di questo standard anche le opzione del "variable exponent" e dell'esponente generalizzato, che avrebbero consentito di spingere l'esponente verso valori ancora più alti. Ma questo, oltre ad essere di dubbia utilità pratica, avrebbe compromesso in maniera inaccettabile il livello di precisione della mantissa e, addirittura, anche dell'esponente!