Punto Flotante - stefano-sosac/arquitectura-de-computadoras GitHub Wiki
Punto Flotante
Valores en Punto Flotante
Los valores numéricos en punto flotante se pueden representar en precisión simple (32 bits) o en precisión doble (64 bits). En C, las variables con precisión simple son declaradas como float
y las variables con precisión doble son declaradas como double
.
float a // a es una variable float de 32 bits
double b // b es una variable double de 64 bits
float *c // c es un puntero a una variable float
double *d // d es un puntero a una variable double
La representación de estos números se basa en estandares para indicar la distribución de la parte entera y la parte decimal. El formato que ha ganado mayor popularidad es el estandar IEEE 754.
IEEE 754 Formatos de Punto Flotante
IEEE 754 define varios formatos de punto flotante, siendo los más comunes los de precisión simple y doble. La representación de un número en punto flotante según el estándar IEEE 754 se compone de tres partes principales: el signo, el exponente y la mantisa (o fracción). La representación completa de un número en punto flotante es la siguiente:
$$ (-1)^{\text{Signo}} \times 1.\text{Mantisa} \times 2^{\text{Exponente real}} $$
Signo (1 bit)
El bit de signo indica si el número es positivo o negativo.
0
para positivo1
para negativo
Exponente
El exponente se utiliza para representar el rango del número y se almacena en forma sesgada. La ecuación para obtener el exponente real es:
$$ \text{Exponente real} = \text{Exponente almacenado} - \text{Sesgo} $$
Mantisa (Fracción)
La mantisa, también conocida como fracción, contiene los dígitos significativos del número.
Para un número normalizado, el bit implícito (el dígito más significativo de la mantisa) es siempre 1
.
Precisión Simple (32 bits)
El formato de precisión simple utiliza 32 bits divididos en 3 secciones: signo, exponente y fracción (mantisa).
- Signo (1 bit): 0 para positivo, 1 para negativo.
- Exponente (8 bits): Se almacena con un sesgo de 127.
- Fracción (23 bits): Representa la parte significativa de la mantisa, normalizada (1.f).
Precisión Doble (64 bits)
El formato de precisión doble amplía la precisión utilizando 64 bits, también divididos en signo, exponente y fracción.
- Signo (1 bit): Al igual que en precisión simple, 0 es positivo y 1 es negativo.
- Exponente (11 bits): Se almacena con un sesgo de 1023.
- Fracción (52 bits): Representa la parte significativa de la mantisa, con la misma normalización (1.f).
Estas estructuras permiten una amplia gama de números a representar, desde muy pequeños hasta muy grandes, manteniendo la precisión y eficiencia en cálculos numéricos.
Ejemplo
Tomemos el número en precisión simple con el siguiente bit de signo, exponente y mantisa:
- Signo:
0
- Exponente almacenado:
10000001
(binario) o129
(decimal) - Mantisa:
10100000000000000000000
(binario)
Primero, convertimos el exponente almacenado a decimal y le restamos el sesgo para obtener el exponente real:
$$ \text{Exponente real} = 129 - 127 = 2 $$
Entonces, aplicamos la fórmula para obtener el valor del número:
$$ (-1)^0 \times 1.10100000000000000000000 \times 2^2 $$
Esto resulta en el número decimal 5.25
.
Operaciones en punto flotante en el CPU
Los primeros CPU (8086/8088) no podían ejecutar operaciones en punto flotante por sí mismos, y para poder hacerlo hacían uso de un coprocesador externo llamado 8087. Cuando un procesador no disponía del 8087 podía desarrollar operaciones en punto flotante mediante el software. Esta disposición se mantuvo hasta que llegó la familia 486, y el coprocesador se integró al procesador. El 8087 manejaba un conjunto de instrucciones que manipulaban una pila de registros de 80 bits. Estas instrucciones aún forman parte de los CPU actuales; sin embargo, ahora existe un nuevo conjunto de instrucciones más eficiente llamado SSE (Streaming SIMD Extensions) que permite ejecutar operaciones en punto flotante con 16 registros dedicados.
Si el lector revisa los manuales Intel-64 o IA-32, se encontrará con instrucciones como fadd
, y con registros como ST(0), ST(1), ST(2), ...
Estos elementos son del coprocesador matemático; sin embargo, la presente guía se enfocará en presentar los elementos de trabajo, instrucciones y registros, relacionados con la unidad SIMD (Single Instruction Multiple Data) del procesador.
Registros de Punto Flotante
Hay un conjunto dedicado de registros, referidos como registros XMM, empleados para dar soporte a las instrucciones en punto flotante. Las instrucciones en punto flotante necesariamente se tienen que usar con estos registros. Los XMM son de 128 bits, pero en los últimos procesadores son de 256 bits. Hay 16 registros XMM que van desde xmm0
hasta xmm15
. Estos registros pueden ser empleados para instrucciones que operan sobre un único valor (escalares) o en un conjunto de elementos (vectoriales), pero para esta oportunidad solo se empleará los últimos 32 o 64 bits de estos registros pues solo se van a realizar operaciones escalares.
Instrucciones de Punto Flotante
La presentación de las instrucciones para operaciones de punto flotante será breve. Solo serán cubiertas las más básicas y serán presentadas en el siguiente orden:
- Instruciones de Transferencia de Datos.
- Instrucciones de Conversión.
- Instrucciones Aritméticas de Punto Flotante.
- Intrucciones de Control de Punto Flotante.
Para una lista completa de las instrucciones se puede revisar el siguiente enlace.
Instruciones de Transferencia de Datos Escalares
Estas instrucciones permiten transferir datos de una posición de memoria a un registro, y de un registro a una posición de memoria y de un registro a otro registro. Hay dos instrucciones para mover escalares en punto flotante: movss
para mover valores de 32 bits (float
) y movsd
para mover valores de 64 bits (double
).
movss xmm0, [a] ; mover el valor en a al registro xmm0
movsd [b], xmm1 ; mover el valor en el registro xmm1 a b
movss xmm2, xmm0 ; mover el valor en xmm0 a xmm2
Instruciones de Conversión
Cuando se requiere emplear enteros en una operación en punto flotante, los enteros deben ser convertidos primero a punto flotante. De igual manera, si se requieren en una misma operación valores de precisión simple y precisión doble, se deberá realizar previamente una operación de conversión para que ambos operandos compartan el mismo tipo de dato.
Conversión entre Operandos Punto Flotante de distinto tamaño
Hay dos instrucciones para convertir operandos escalares en punto flotante: cvtss2sd
para convertir valores de 32 bits a 64 bits y cvtsd2ss
para convertir valores de 64 bits a 32 bits.
cvtss2sd xmm0, [x] ; convertir el float en x a double en xmm0
cvtsd2ss xmm0, xmm0 ; el float en xmm0 a double en xmm0
Conversión de/a Punto Flotante de/a Entero
Cuando el método para convertir números en punto flotante a enteros es por redondeo hay dos intrucciones: cvtss2si
para convertir un float
a entero y cvtsd2si
para convertir un double
a entero. Cuando el método de conversión es por truncamiento hay otras dos instrucciones: cvttss2si
y cvttsd2si
. Asimismo, para convertir números enteros a punto flotante hay dos instrucciones: cvtsi2ss
y cvtsi2sd
. ↑
cvtss2si eax, xmm0 ; float en xmm0 a int en eax
cvtsi2sd xmm0, rax ; long en rax a double en xmm0
cvtsi2sd xmm0, [x] ; int en x a double en xmm0
Instruciones Aritméticas de Punto Flotante
Estas intrucciones permiten ejecutar operaciones de suma, resta, multiplicación, división y raíz cuadrada con operandos en punto flotante de 32 o 64 bits.
addss xmm0, [a] ; xmm0 <- xmm0 + [a] (float)
addss xmm0, xmm1 ; xmm0 <- xmm0 + xmm1 (float)
addsd xmm0, [b] ; xmm0 <- xmm0 + [b] (double)
addsd xmm0, xmm1 ; xmm0 <- xmm0 + xmm1 (double)
subss xmm0, [a] ; xmm0 <- xmm0 - [a] (float)
subss xmm0, xmm1 ; xmm0 <- xmm0 - xmm1 (float)
subsd xmm0, [b] ; xmm0 <- xmm0 - [b] (double)
subsd xmm0, xmm1 ; xmm0 <- xmm0 - xmm1 (double)
mulss xmm0, [a] ; xmm0 <- xmm0 * [a] (float)
mulss xmm0, xmm1 ; xmm0 <- xmm0 * xmm1 (float)
mulsd xmm0, [b] ; xmm0 <- xmm0 * [b] (double)
mulsd xmm0, xmm1 ; xmm0 <- xmm0 * xmm1 (double)
divss xmm0, [a] ; xmm0 <- xmm0 / [a] (float)
divss xmm0, xmm1 ; xmm0 <- xmm0 / xmm1 (float)
divsd xmm0, [b] ; xmm0 <- xmm0 / [b] (double)
divsd xmm0, xmm1 ; xmm0 <- xmm0 / xmm1 (double)
sqrtss xmm0, [a] ; xmm0 <- ([a])^0.5 (float)
sqrtss xmm0, xmm1 ; xmm0 <- (xmm1)^0.5 (float)
sqrtsd xmm0, [b] ; xmm0 <- ([b])^0.5 (double)
sqrtsd xmm0, xmm1 ; xmm0 <- (xmm1)^0.5 (double)
Notar que el operando de destino siempre es un registro. ↑
Instruciones de Control de Punto Flotante
Las instrucciones de control son aquellas que permiten implementar estructuras selectivas (IF - ELSE) e iterativas (FOR - WHILE). La instrucción cmp
que se empleaba con enteros no funcionará con operandos en punto flotante. Las instrucciones de comparación tendrán ambos operandos en punto flotante, y al igual que en el caso de los enteros el resultado será almacenado el registro de banderas.
Comparaciones en Punto Flotante
La forma general de las operaciones de comparación es una de las siguientes:
ucomiss Rxmm, op2
ucomisd Rxmm, op2
Donde Rxmm
y op2
son operandos en punto flotante y deben ser del mismo tamaño. Ninguno de los operandos será alterado por las operaciones de comparación. El operando Rxmm
debe ser un registro xmm
, y el operando op2
puede ser un registro xmm
o el contenido de una posición de memoria.
En los siguientes ejemplos se pueden apreciar algunas de las operaciones de saltos de control que se pueden realizar:
je label ; jump equal si op1 == op2
jne label ; jump not equal si op1 != op2
jb label ; jump below than si op1 < op2
jbe label ; jump below or equal si op1 <= op2
ja label ; jump above than si op1 > op2
jae label ; jump above or equal si op1 >= op2
Se debe tener en cuenta que las últimas cuatro instrucciones operarán como si fueran números sin signo.