Cuando se diseñó el coprocesador numérico 8087, era bastante común que los idiomas realizaran todas las matemáticas de punto flotante utilizando el tipo de mayor precisión, y solo redondean el resultado a menor precisión al asignarlo a una variable de menor precisión. En el estándar C original, por ejemplo, la secuencia:
float a = 16777216, b = 0.125, c = -16777216;
float d = a+b+c;
promovería a
y b
que double
, agregarlos, promover c
a double
, agregarlo, y luego almacenar el resultado redondeado al float
. Aunque en muchos casos hubiera sido más rápido para un compilador generar código que realizaría operaciones directamente en tipo float
, era más simple tener un conjunto de rutinas de punto flotante que operarían solo en tipo double
, junto con rutinas para convertir a / desde float
, que tener conjuntos separados de rutinas para manejar operaciones en float
y double
. El 8087 se diseñó en torno a ese enfoque de la aritmética, realizando todas las operaciones aritméticas utilizando un tipo de punto flotante de 80 bits [80 bits probablemente se eligió porque:
En muchos procesadores de 16 y 32 bits, es más rápido trabajar con una mantisa de 64 bits y un exponente separado que trabajar con un valor que divide un byte entre la mantisa y el exponente.
Es muy difícil realizar cálculos que sean precisos a la precisión total de los tipos numéricos que uno está usando; si uno está tratando de calcular, por ejemplo, algo como log10 (x), es más fácil y rápido calcular un resultado con una precisión de 100ulp de un tipo de 80 bits que calcular un resultado con una precisión de 1ulp de 64 bits tipo, y redondeando el resultado anterior a una precisión de 64 bits producirá un valor de 64 bits que es más preciso que el segundo.
Desafortunadamente, las versiones futuras del lenguaje cambiaron la semántica de cómo deberían funcionar los tipos de punto flotante; mientras que la semántica 8087 habría sido muy agradable si los lenguajes los hubieran apoyado de manera consistente, si las funciones f1 (), f2 (), etc. devuelven el tipo float
, muchos autores del compilador se encargarían de crear long double
un alias para el tipo doble de 64 bits en lugar del tipo de 80 bits del compilador (y no proporcionar otros medios para crear variables de 80 bits), y evaluar arbitrariamente algo como:
double f = f1()*f2() - f3()*f4();
en cualquiera de las siguientes formas:
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();
Tenga en cuenta que si f3 y f4 devuelven los mismos valores que f1 y f2, respectivamente, la expresión original debería devolver claramente cero, pero muchas de las últimas expresiones pueden no hacerlo. Esto llevó a las personas a condenar la "precisión adicional" del 8087 a pesar de que la última formulación sería generalmente superior a la tercera y, con un código que usara el tipo doble extendido apropiadamente, rara vez sería inferior.
En los años intermedios, Intel ha respondido a la tendencia del lenguaje (en mi humilde opinión) de forzar a que los resultados intermedios se redondeen a la precisión de los operandos diseñando sus procesadores posteriores para favorecer ese comportamiento, en detrimento del código que se beneficiaría con el uso de niveles superiores. precisión en cálculos intermedios.
sizeof(int)
igual a 1 debe requerir que se firme el tipochar
(ya que unint
debe poder contener todos los valores de tipochar
). He escrito código para una máquina dondechar
yint
son enteros con signo de 16 bits; Las mayores dificultades son que no se pueden usar uniones para la conversión de tipos, y el almacenamiento eficiente de una gran cantidad de bytes requiere el empaquetado y desempaquetado manual. Esos problemas son menores en comparación con la posibilidad en C de que sizeof (int) == sizeof (long), ya que ...unsigned int
valores. C99 mejoró esa situación, pero antes de C99 no había una forma segura y segura de un solo paso para comparar un valor potencialmente negativo con un valor de tipounsigned int
(uno tendría que probar si el número era negativo antes de hacer la comparación).