Quando è stato progettato il coprocessore numerico 8087, era abbastanza comune per le lingue eseguire tutti i calcoli matematici a virgola mobile usando il tipo di massima precisione e arrotondare il risultato solo a una precisione inferiore assegnandolo a una variabile di precisione inferiore. Nello standard C originale, ad esempio, la sequenza:
float a = 16777216, b = 0.125, c = -16777216;
float d = a+b+c;
promuovere a
e b
a double
, aggiungerli, promuovere c
a double
, aggiungerlo e quindi archiviare il risultato arrotondato a float
. Anche se in molti casi sarebbe stato più veloce per un compilatore generare codice che eseguisse operazioni direttamente sul tipo float
, era più semplice avere un insieme di routine a virgola mobile che operassero solo sul tipo double
, lungo con routine da convertire in / da float
, che avere serie separate di routine per gestire le operazioni su float
e double
. L'8087 è stato progettato attorno a questo approccio all'aritmetica, eseguendo tutte le operazioni aritmetiche utilizzando un tipo a virgola mobile a 80 bit [80 bit è stato probabilmente scelto perché:
-
Su molti processori a 16 e 32 bit, è più veloce lavorare con una mantissa a 64 bit e un esponente separato piuttosto che lavorare con valori che dividono un byte tra la mantissa e l'esponente.
-
È molto difficile eseguire calcoli accurati alla precisione dei tipi numerici che si stanno utilizzando; se si sta provando ad es. calcola qualcosa come log10 (x), è più facile e veloce calcolare un risultato che sia accurato all'interno di 100ulp di un tipo a 80-bit piuttosto che calcolare un risultato che sia preciso all'interno di 1ulp di un tipo a 64-bit e che arrotonda il primo il risultato della precisione a 64 bit produrrà un valore a 64 bit più accurato del secondo.
Sfortunatamente, le versioni future del linguaggio hanno cambiato la semantica di come dovrebbero funzionare i tipi in virgola mobile; mentre la semantica 8087 sarebbe stata molto carina se le lingue li avessero supportati in modo consistente, se le funzioni f1 (), f2 (), ecc. restituiscono il tipo float
, molti autori di compilatori prenderanno su di loro stessi per rendere long double
un alias per il doppio tipo a 64 bit anziché il tipo a 80 bit del compilatore (e non fornisce altri mezzi per creare variabili a 80 bit) e per valutare arbitrariamente qualcosa del tipo:
double f = f1()*f2() - f3()*f4();
in uno dei seguenti modi:
double f = (float)(f1()*f2()) - (extended_double)f3()*f4();
double f = (extended_double)f1()*f2() - (float)(f3()*f4());
double f = (float)(f1()*f2()) - (float)(f3()*f4());
double f = (extended_double)f1()*f2() - (extended_double)f3()*f4();
Si noti che se f3 e f4 restituiscono gli stessi valori di f1 e f2, rispettivamente, l'espressione originale dovrebbe restituire chiaramente zero, ma molte delle ultime espressioni potrebbero non esserlo. Ciò ha portato le persone a condannare la "precisione extra" dell'8087 anche se l'ultima formulazione sarebbe generalmente superiore alla terza e - con il codice che utilizzava il doppio tipo esteso in modo appropriato - sarebbe raramente inferiore.
Negli anni successivi, Intel ha risposto alla tendenza del linguaggio (IMHO sfavorevole) a forzare i risultati intermedi ad arrotondare la precisione degli operandi progettando i loro processori successivi in modo da favorire tale comportamento, a scapito del codice che andrebbe a vantaggio dall'uso di maggiore precisione sui calcoli intermedi.