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 positivo
  • 1 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) o 129 (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.