Especificación IEEE-754, para representar valores decimales en
Transcripción
Especificación IEEE-754, para representar valores decimales en
Especificación IEEE-754, para representar valores decimales en punto flotante de simple precisión. Ing. Cosme Rafael Marcano Gamero, MSc, DEA. [email protected] En este escrito se explica la especificación utilizada por la mayoría de los lenguajes de programación disponibles en el mercado para representación los así llamados números reales, en los dispositivos de almacenamiento, ya sea primario (RAM) o secundario (discos duros, etc.). La especificación IEEE-754 establece que los 32 bits que ocupa un valor decimal, en formato de simple precisión, es la siguiente: Bit31: bit de signo: 1 = -, 0 = + Bit30-23: 8 bits para el exponente, desplazado en 127. Bit22-0: 23 bits para la mantisa normalizada. EJEMPLO 1. Tomemos como ejemplo a -1. En primer lugar, la parte entera (sin signo) de -1.0 es 1. Como no hay que mover la coma decimal, 1 * 20 exp onente 127 0 127 01111111b Por último, como -1 es negativo, el bit de signo, es decir, el bit 31, será igual a 1. Entonces: B 1 3 1 0 3 0 F 1 1 1 1 1 1 1 2 3 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 1 0 2 En vista de que los valores usualmente son almacenados terminando con el byte más significativo (lo cual es referido a veces como bigendian), entonces la representación de -1,0 en formato IEEE-754 sería igual a BF800000, y se colocaría en memoria o en archivos binarios como: 000080BF, como se puede ver en la siguiente figura. Cosme Rafael Marcano Gamero. Figura 1. Representación en un archivo binario (20reales.bin), de veintidós valores decimales en formato IEEE-754. En la Figura 1 aparecen las representaciones de los siguientes valores decimales en formato IEEE-754: -1, -0.875, -0.75, -0.625, -0.5, -0.375, 0.25, -0.125, 0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1, 1.125, 1.25, 1.375, 1.7 y 123.45. Nótese que cada valor ocupa cuatro bytes, es decir, 32 bits. Cada byte aparece representado en forma de dos dígitos hexadecimales. Así, por ejemplo, la representación de -1.0, o sea, 000080BF, corresponde a los cuatro primeros bytes a partir del inicio del archivo (posición 00000000). EJEMPLO 2. Como otro ejemplo, hallemos la representación en formato IEEE-754 del penúltimo valor almacenado en este archivo, es decir, 1.7. En primer lugar, la parte entera de 1.7 es 1. Para normalizar este valor no hay que mover la coma decimal, entonces: 1 * 20 exp onente 127 0 127 01111111b La conversión de la parte decimal (0.7), viene a ser: Bit x acumulado i x * 2 i 1,2,3,... 1 1* 2 1 0.5000000 0 0*2 2 0.5000000 1 1* 2 3 0.625000000 1 1* 2 5 0*2 4 0.687500000 0 0.687500000 Cosme Rafael Marcano Gamero. 0 0*2 6 0.687500000 1 1* 2 7 0.695312500 8 0.699218750 0 1* 2 9 0*2 0 0*2 10 0.699218750 1 1* 2 11 0.699707031 1 1* 2 12 0.699951172 0 0*2 13 0.699951172 0 0*2 15 1* 2 14 0.699951172 0.699996948 1 1 0.699218750 0.699981689 1 1* 2 16 0 0*2 17 0.699996948 0 0*2 18 0.699996948 1 1* 2 19 0.699998856 1 1* 2 20 0.699999809 0*2 21 0.699999809 1 1* 2 22 0.699999928 0 0*2 23 0.699999928 0 Parte decimal convertida: 0.699999928 Por último, como 1.7 es positivo, el bit de signo, es decir, el bit 31, será igual a 0. Entonces: 3 0 3 1 0 3 0 F D 1 1 1 1 1 1 1 2 3 1 2 2 9 9 9 9 A 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 1 0 1 0 Y se almacenará en el archivo con la siguiente apariencia: como se muestra en la Figura 1. 9A 99 D9 3F, Nótese que en ambos ejemplos se omitió, de acuerdo a lo establecido en la especificación IEEE-754, la representación del “1.”, después de haberse normalizado la mantisa. Recuerde que cada bit de la representación de la parte decimal del valor que se desea convertir al formato IEEE-754, constituye un factor que multiplica a potencias negativas de dos. Esto es, si los 23 bits de la parte decimal de 1.7 (que, en este caso es exactamente igual a la mantisa) son los siguientes: Cosme Rafael Marcano Gamero. 1 0 1 1 0 Entonces, 1 el 2 0 1 1 0 0 1 1 equivalente 3 0 0 decimal 4 1 * 2 0 * 2 1 * 2 1 * 2 ... 1 * 2 22 1 1 0 de 0*2 23 0 esa 1 1 0 1 0 representación será: 0.7 EJEMPLO 3. Por último, como un ejemplo de conversión de un valor cuya parte entera requiere más de un dígito binario, tomemos el 123.45, que es el último valor almacenado en el archivo 20reales.bin (ver Figura 1). En este caso, la parte entera es igual a 123 = 0111 1011b (7Bh). Para obtener la representación de la parte decimal, observamos que: Bit x acumulado i x * 2 i 1,2,3,... 0*2 1 0.000000000 1 1* 2 2 0.250000000 1 1* 2 3 0.375000000 1 1* 2 4 0.437500000 0 0*2 5 0.437500000 0*2 6 0.437500000 1 1* 2 7 0.445312500 1 1* 2 8 0.449218750 0*2 9 0.449218750 0.449218750 0.449707031 0 0 0 0 0*2 10 1 1* 2 11 1 1* 2 12 0.449951172 0 0*2 13 0.449951172 0 0*2 14 0.449951172 1 1* 2 15 0.449981689 1 1* 2 16 0.449989319 0 0*2 17 0.449989319 Parte decimal convertida: 01110011001100110 = 0.449989319 Entonces: 123.45 = 111 1011.01110011001100110 Al normalizar, 111 1011 queda: 1.111011 * 2 exp onente 127 6 133 10000101b 6 Quitando el “1.”, y agregando la parte decimal, nos queda: Cosme Rafael Marcano Gamero. 1110110111 0011 0011 0010 El bit de signo es 0, por ser 123.45 un valor positivo. 0 1 4 2 0 0 0 0 1 0 1 3 1 3 0 2 3 F 6 E 6 6 6 1 1 1 0 1 1 0 1 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 1 0 2 2 Finalmente, la representación de 123.45 en formato IEEE-754, que resultó ser igual a 42 F6 E6 66, es almacenado como 66 E6 F6 42, de acuerdo a lo mostrado en la Figura 1. COMENTARIOS SOBRE EL PROGRAMA QUE GENERÓ EL ARCHIO BIMARIO 20reales.bin Inclusión de la librería en donde se definen la clase de objeto #include <fstream> Definición de la estructura requerida para escribir y leer variables de tipo real (float) en/desde un archivo binario en C++: struct real { float }; valor; Declaración de un apuntador a la estructura requerida para escribir y leer variables de tipo real (float) en/desde un archivo binario en C++: real* preal = new real(); Declaración de una variable que contendrá el nombre del archivo binario en donde se almacenarán los valores reales: static const char *BINARIO = "20reales.bin"; Apertura del archivo de escritura. Nóyese que se abrirá con el nombre “salida”. Con este nombre (manejador) se hará referencia al archivo físico “20reales.bin” dentro del programa. Nótese también que se abre con los atributos ios_base::out para indicar que es un archivo de salida (sobre el cual se escribirán datos), ios_base::binary para indicar que los datos deben ser almacenados en formato binario, y el atributo ios_base::trunc, para indicar que los datos deben ser truncados (no redondeados) a la longitud exacta en bytes que ocupen. std::ofstream salida(BINARIO, ios_base::out | ios_base::binary | ios_base::trunc); if (salida==NULL){ cout << "Error: no pude abrir Archivo de salida." << endl; return(-1); Cosme Rafael Marcano Gamero. } Asignación del valor inicial de la variable real a ser escrita en el archivo binario. preal->valor=-1.0; for (int contador=0; contador<20; contador++){ // escribir el contenido de la variable en el archivo salida.write((const char*)preal, sizeof(real)); // mostrar en pantalla el mismo valor printf("\n%10.7f", preal->valor); // incrementar en 0.125 el valor de la variable. Esto quiere decir que // el segundo valor a almacenar en el archivo será -0.875, luego, // -0.750, etc. preal->valor+= 0.125; } preal->valor+= 0.2; // aquí alcanza el valor de 1.7 printf("\n%10.7f", preal->valor); salida.write((const char*)preal, sizeof(real)); // ejemplo con parte entera de más de un dígito binario preal->valor = 123.45; printf("\n%10.7f", preal->valor); salida.write((const char*)preal, sizeof(real)); // cerrar archive explícitamente (aunque C++ cierra automáticamente los // archives que estén abiertos al finalizar la ejecución del programa). salida.close(); La lectura de variables reales desde un archivo binario se realiza de manera similar a la escritura y queda planteado como ejercicio. MOTA: Para poder utilizar printf("\n%10.7f", preal->valor), se debe incluir el archivo de prototipos stdo.h el cual no es orioui de C++, sino de C estándar. Esta función, sin embargo, permite controlar a voluntad del programador (y las características del lenguaje de programación, por supuesto) el número de posiciones decimales que se quiere mostrar en pantalla. En este sentido, es más potente que la función std::cout de C++. Para una comparación más detallada entre printf()y std::cout se puede consultar el artículo “Comentarios sobre printf()y std::cout”m que está disponible en cosmemarcano.wordpress.com. COMENTARIO FINAL. Afortunadamente, la conversión de los números con decimales (también llamados números reales) es automática y transparente al programador en lenguaje de de alto nivel, como Fortran, Pascal y C. No obstante, a la hora de diseñar un prototipo de hardware que se desee conectar a una computadora u otro circuito digital estándar, es necesario respetar el formato utilizado para cada tipo de datos, de manera que se pueda compartir información entre tales dispositivos. Cosme Rafael Marcano Gamero.