El coprocesador matemático 8087

Por Dario Alejandro Alpern

Introducción

El procesador de datos numérico (NDP) 8087 aumenta el juego de instrucciones del 8086/8088 mejorando su capacidad de tratamiento de números. Se utiliza como procesador paralelo junto al 8086/8088 añadiendo 8 registros de coma flotante de 80 bits así como instrucciones adicionales. Utiliza su propia cola de instrucciones para controlar el flujo de instrucciones del 8086/8088, ejecutando sólo aquellas instrucciones que le corresponden, e ignorando las destinadas a la CPU 8086/8088. El 8086/8088 deberá funcionar en modo máximo para poder acomodar el 8087. Las instrucciones del NDP 8087 incluyen un juego completo de funciones aritméticas así como un potente núcleo de funciones exponenciales, logarítmicas y trigonométricas. Utiliza un formato interno de números en coma flotante de 80 bits con el cual gestiona siete formatos exteriores.

Como detalle constructivo, cabe mencionar que el 8087 posee 45.000 transistores y consume 3 watt.

Los números y su tratamiento

Hay dos tipos de números que aparecen normalmente durante el cálculo: los números enteros y los números reales. Aunque los enteros no dejan de ser un subconjunto de los reales, la computadora trabaja de formas distintas con ambos. Los enteros son fáciles de tratar para la computadora. Los chips microprocesadores de propósito general trabajan con números enteros utilizando la representación binaria de números en complemento a dos. Pueden trabajar incluso con números que excedan el tamaño de la palabra a base de fragmentar los números en unidades más pequeñas. Es lo que se llama aritmética de precisión múltiple. Los números reales, sin embargo, son más difíciles. En primer lugar, la mayoría de ellos nunca pueden representarse exactamente. La representación en coma flotante permite una representación aproximada muy buena en la práctica de los números reales. La representación en coma flotante es en el fondo una variación de la notación científica que puede verse en el visualizador de cualquier calculadora. Con este sistema, la representación de un número consta de tres partes: el signo, el exponente y la mantisa. Antes de continuar, veremos por qué es necesaria esta representación. Para los principiantes en el tema, consideremos el siguiente ejemplo de notación científica: 4,1468E2. En este ejemplo el signo es positivo (porque no está se supone positivo), la mantisa es 4,1468 y el exponente 2. El valor representado por este número es: 4,1468 x 10 ^ 2 = 414,68.

Precisión y rango

En la representación de números reales aparecen dos problemas fundamentales: la precisión y el rango.

Por precisión se entiende la exactitud de un número. En la representación de coma flotante, la mantisa es la encargada de la precisión. La mantisa contiene los dígitos significativos del número independientemente de dónde esté colocada la coma decimal. Si queremos aumentar la precisión de un esquema de representación de punto flotante, basta con añadir dígitos a la mantisa.

La precisión no es problema en el caso de los enteros, puesto que todo entero viene representado exactamente por su representación en complemento a dos. La representación precisa de los números reales sí es un problema ya que la mayoría de ellos tiene infinitos dígitos, cosa imposible de representar exactamente en una máquina con un número finito de componentes. Puesto que las computadoras permiten almacenar un número finito de dígitos, la representación de los números reales debe realizarse necesariamente por medio de aproximaciones.

El rango está relacionado con el tamaño de los números que se pueden representar. En los enteros, el rango depende del número de bits que se utilicen. Por ejemplo, con 16 bits pueden representarse números comprendidos entre -32768 y 32767. Para representar todos los enteros sería de nuevo necesario un número infinito de bits. Como puede verse, incluso los enteros tienen un rango restringido. En la notación de coma flotante, es el exponente el que fija el rango. Separando los problemas de precisión y de rango, la notación en coma flotante permite obtener rangos muy grandes con precisión razonable. El rango sigue siendo finito, porque sólo se puede representar un número finito de dígitos del exponente, pero tales dígitos permiten representar un número bastante grande de una forma compacta.

Implementación de la representación en coma flotante

Hay muchas formas de implementar la representación en coma flotante. Intel sigue el método propuesto por el instituto de normalización IEEE. Para ver cómo trabaja, volvamos de nuevo a la notación científica. Por ejemplo, en notación científica decimal el número 414,68 suele escribirse como 4,1468E2 y el número -0,00345 suele representarse como -3,45E-3. En cada caso el número consta de un signo (en el primer número no aparece por ser positivo), una mantisa y un exponente. La "E" indica sencillamente "exponente" y puede leerse como "diez a la". Normalmente, la coma decimal se coloca a la derecha del primer dígito significativo. Cuando esto ocurre, se dice que el número está normalizado. El número cero es un caso particular que no admite normalización. Puede representarse por una mantisa cero. El exponente en tal caso es arbitrario, aunque se siguen algunos convenios dependiendo de la implementación particular.

En contraste con la notación científica usual que utiliza la base 10, las computadoras utilizan la base 2 a la vez como base de la expresión exponencial, y como base para representar la mantisa y el exponente. Así, por ejemplo, el número 5,325 sería 4 + 1 + 1/4 + 1/8 = 101,111 (base 2) y se escribe en notación científica "binaria" como +1,01011E2. En este caso "E" debe leerse como "2 a la", y el incrementar o decrementar el exponente significa desplazar convenientemente la coma binaria.

Cuando se almacena un número binario en coma flotante en la máquina, se utiliza un bit para el signo, varios para la mantisa y varios para el exponente. Normalmente el bit de signo precede a los demás, después vienen los bits de exponente y finalmente los de la mantisa. La forma más común de guardar el exponente es adicionarle una constante, que recibe el nombre de "exceso". El número que se guarda recibe el nombre de "exponente en notación de exceso". A la inversa, dada la representación de un exponente, para recuperar su valor verdadero, basta con restarle el exceso. Como exceso se toma un número cuyo valor es aproximadamente igual a la mitad del rango de los posibles exponentes, para poder representar más o menos la misma cantidad de exponentes positivos que negativos.

Como ejemplo práctico, veremos cómo se representan los números reales en los registros del NDP 8087. Este formato particular recibe el nombre de formato real temporal. Todos los datos en el interior del NDP se almacenan de esta manera.

Descripción Signo Exponente Mantisa
Bits 79 78 ... 64 63 ... 0

El formato real temporal requiere 80 bits: uno para el signo, 15 para el exponente y 64 para la mantisa. El exceso es 2 ^ 14 - 1 = 16383. El menor valor positivo que se puede representar es 2 ^ -16382 = 3,36 x 10 ^ -4932, mientras que el mayor valor positivo representable es igual a 2 ^ 16384 = 1,19 x 10 ^ 4932.

El NDP 8087 como procesador paralelo

El NDP 8087 actúa también como procesador paralelo. Esto es, comparte con la CPU el mismo bus, y el mismo flujo de instrucciones.

Las instrucciones del NDP aparecen mezcladas con el flujo de instrucciones de la CPU. Sin embargo, cada cual selecciona y ejecuta sólo las que le corresponden. Todas las instrucciones correspondientes al NDP comienzan con una variante de la instrucción ESC del 8086/8088. Esta es una instrucción ficticia, con un operando ficticio que puede especificarse con cualquiera de los 24 modos de direccionamiento a memoria y otro operando ficticio que no es más que un registro.

Cada instrucción que lee la CPU, también lo lee el NDP. Cuando la CPU lee una instrucción ESC, el NDP se da por enterado que pronto tendrá que ponerse a trabajar. El NDP lee el código de operación, mientras que la CPU calcula la dirección de memoria, la que pone en el bus de direcciones. La CPU finalmente pasa el control al NDP, que extrae los bytes de datos necesarios, hace los cálculos correspondientes, y coloca los resultados sobre el bus.

El NDP como ampliación del 8086/8088

El NDP 8087 amplía el juego de instrucciones del 8086/8088, incluyendo operaciones en coma flotante. Estas nuevas instrucciones implementan por hardware el paquete Intel de tratamiento de números en punto flotante, proveyendo un método mucho mejor de ejecución de estos algoritmos. Dicho de otra manera, las rutinas de tratamiento de números en coma flotante pueden realizarse a velocidades muy altas. Intel estima que el NDP 8087 ejecuta las operaciones de coma flotante hasta 100 veces más rápidamente que una CPU 8086 que los realizase por software. A continuación puede verse una comparación entre la velocidad del 8087 y del paquete de software del 8086. En ambos casos, los procesadores funcionaban a 5 MHz. Los tiempos están dados en microsegundos.

Operación en punto flotante 8087 8086
Suma en signo y magnitud 14 1600
Resta en signo y magnitud 18 1600
Multiplicación (precisión simple) 19 1600
Multiplicación (precisión doble) 27 2100
División 39 3200
Comparación 9 1300
Carga (doble precisión) 10 1700
Almacena (doble precisión) 21 1200
Raíz cuadrada 36 19600
Tangente 90 13000
Exponenciación 100 17100

El NDP amplía también el conjunto de registros de la CPU 8086/8088. Los registros de la CPU están en un chip (el 8086/8088, y hay ocho registros de coma flotante de 80 bits en el otro (el 8087). Dichos registros guardan números en el formato real temporal antes estudiado.

El coprocesador matemático trabaja simultáneamente con el procesador principal. Sin embargo, como el primero no puede manejar entrada o salida a dispositivos, la mayoría de los datos los origina la CPU. La CPU y el NDP tienen sus propios registros, que son completamente separados e inaccesibles al otro chip. Los datos se intercambian a través de la memoria, ya que ambos chips pueden acceder la memoria.

Los ocho registros se organizan como una pila. Una pila es tal vez la mejor manera de organizar los datos para evaluar expresiones algebraicas complejas. Los nombres de estos registros son ST(0), ST(1), ST(2), ..., ST(7). El nombre simbólico ST (Stack Top) es equivalente a ST(0). Al poner (push) un número en la pila, ST(0) contendrá el número recién ingresado, ST(1) será el valor anterior de ST(0), ST(2) será el valor anterior de ST(1), y así sucesivamente, con lo que se perderá el valor anterior de ST(7). El NDP 8087 tiene algunos registros más: un registro de estado de 16 bits, un registro de modo de 16 bits, un registro indicador de 8 bits y cuatro registros de 16 bits de salvaguarda de instrucciones y punteros a datos para la gestión de condiciones excepcionales.

El NDP es también una ampliación hardware desde el momento que no es autosuficiente. El 8087 necesita tener al lado un 8086 o un 8088 que le proporcione datos y direcciones, y controle el bus que a su vez suministrará instrucciones y operandos.

Hay varias líneas de control que interconectan el NDP y la CPU: la señal de TEST del CPU que se conecta a BUSY (ocupado) del NDP, la línea RQ0/GT0 (petición/concesión del manejo del bus), y las señales de estado de la cola (QS1, QS0).

El terminal de TEST del 8086/8088 se conecta al de BUSY del 8087. Esto permite que el 8086/8088 pueda utilizar la instrucción WAIT para sincronizar su actividad con el NDP. La forma correcta de hacerlo es conseguir que el ensamblador genere automáticamente una instrucción WAIT antes de cada instrucción del NDP, y que el programador ponga en el programa una instrucción FWAIT (sinónimo de WAIT) después de cada instrucción del NDP que cargue los datos en memoria para que puedan ser inmediatamente utilizadas por la CPU. Mientras el NDP 8087 está realizando una operación numérica, pone a 1 el terminal BUSY (y por lo tanto el terminal WAIT del CPU). Mientras la CPU ejecuta la instrucción WAIT, suspende toda actividad hasta que el terminal TEST vuelve a su estado normal (cero). Así, la secuencia de una instrucción numérica del NDP seguida de un WAIT de la CPU hace que la CPU llame al NDP y espere hasta que el último acabe.

La línea RQ/GT0 (petición/concesión del manejo del bus) la utiliza el NDP para conseguir el control del bus compartido por el NDP y la CPU. La línea de interconexión es bidireccional. Una señal (petición) del NDP a la CPU indica que el NDP quiere utilizar el bus. Para que el NDP tome control del bus, debe esperar a que la CPU le conteste (con una concesión). Cuando el NDP acaba de usar el bus, envía una señal por el mismo pin indicando que ha terminado. En este protocolo, la CPU juega el papel de amo y el NDP de esclavo. El esclavo es el que pide el bus y el amo es el que lo concede tan pronto como puede.

Hay dos terminales de estado de la cola, QS1 y QS0. Ambas líneas permiten al NDP sincronizar su cola de instrucciones con la de la CPU.

Tipos de datos del NDP 8087

El NDP puede trabajar con siete tipos de datos distintos: enteros de tres longitudes distintas, un tipo de BCD empaquetado, y tres tipos de representación en coma flotante. Los siete tipos de datos se almacenan internamente en el formato real temporal de 80 bits ya discutido. Todos los tipos de datos pueden guardarse con la suficiente precisión en este formato.

Los tipos de datos son los siguientes: palabra entera de 16 bits, entero corto de 32 bits, entero largo de 64 bits (en todos los casos en representación de complemento a dos), BCD empaquetado con 18 dígitos decimales (como son dos dígitos por byte, se requieren diez bytes, estando uno de ellos reservado para el signo), real corto de 32 bits (1 bit de signo, 8 de exponente y 23 de mantisa), real largo de 64 bits (1 de signo, 11 de exponente y 52 de mantisa) y real temporal (1 de signo, 15 de exponente y 64 de mantisa). Como se vio anteriormente, el primer bit de la mantisa siempre es uno (en caso contrario el número no estaría normalizado). Este uno no aparece en los formatos real corto y real largo, por lo que la precisión de la mantisa es de 24 y 53 bits, respectivamente.

Notación polaca inversa y uso de la pila del 8087

Para entender la notación polaca inversa (el inventor de esta notación fue el científico polaco Lukasiewicz) de operaciones algebraicas veremos un ejemplo: sea hallar el valor de la raíz cuadrada de 32 + 42) paso a paso.

Lo primero que se hace es hallar 3 al cuadrado, luego 4 al cuadrado, luego se suman ambos resultados y finalmente se halla la raíz cuadrada. En calculadoras que utilizan la notación polaca inversa (como las Hewlett-Packard) se apretarían las siguientes teclas: 3, x2, ENTER, 4, x2, +, raíz cuadrada. La tecla ENTER en estas calculadoras guardan un resultado intermedio en una pila para poder utilizarlo más adelante (en este caso, en la suma). La tecla + utiliza el número que está en la pantalla y el que se guardó en la pila anteriormente para realizar la suma.

Otro ejemplo más complicado: (2 + 8) / (25 - 53) sería: 2, ENTER, 8, +, ENTER, 25, ENTER, 5, ENTER, 3, x3, -, /.

Veremos la ejecución paso a paso de la secuencia recién mostrada (aquí ST(0) representa la pantalla, ST(1) lo que se puso en la pila mediante ENTER y así sucesivamente):

ST(0) ST(1) ST(2) ST3
2 2
ENTER 2 2
8 8 2
+ 10
ENTER 10 10
25 25 10
ENTER 25 25 10
5 5 25 10
ENTER 5 5 25 10
3 3 5 25 10
x3 125 25 10
- -100 10
/ -0,1

Es fundamental haber entendido ambos ejemplos, ya que el coprocesador trabaja de esta manera (primero se ponen los operandos en la pila y después se realizan las operaciones).

Hay dos operaciones básicas de acceso de datos en la pila del 8087 que son las de extraer (pop) e introducir (push). La introducción funciona de la siguiente manera: tiene un operando que es la fuente. El puntero de la pila se decrementa en uno de manera que apunte a la posición inmediata superior y, a continuación, se el dato se transfiere a este nuevo elemento superior. El extraer funciona de la siguiente manera: en primer lugar se transfiere el dato correspondiente al elemento superior de la pila a su destino, y seguidamente el puntero de pila se incrementa en uno, de manera que apunta ahora al nuevo elemento superior de la pila (que está en una posición por encima del anterior).

En el NDP, la pila está numerada respecto a su elemento superior. Dicho elemento se denota por ST(0), o simplemente ST, y las posiciones inferiores se denotan respectivamente por ST(1), ST(2), ..., ST(7).

Registro del 8087Posición relativa con
respecto al puntero de pila
R0ST(4)
R1ST(5)
R2ST(6)
R3ST(7)
R4STPuntero de pila
R5ST(1)
R6ST(2)
R7ST(3)

No es posible acceder a Rn por programa, sólo a ST(n).

Juego de instrucciones del 8087

El NDP tiene una gran cantidad de instrucciones distintas, incluyendo transferencia y conversión de datos, comparaciones, las cuatro operaciones básicas, trigonométricas, exponenciales y logaritmos, carga de constantes especiales y de control.

Instrucciones de transferencia de números

FLD mem: Introduce una copia de mem en ST. La fuente debe ser un número real en punto flotante de 4, 8 ó 10 bytes. Este operando se transforma automáticamente al formato real temporal.

FLD ST(num): Introduce una copia de ST(num) en ST.

FILD mem: Introduce una copia de mem en ST. La fuente debe ser un operando de memoria de 2, 4 u 8 bytes, que se interpreta como un número entero y se convierte al formato real temporal.

FBLD mem: Introduce una copia de mem en ST. La fuente debe ser un operando de 10 bytes, que se interpreta como un valor BCD empaquetado y se convierte al formato real temporal.

FST mem: Copia ST a mem sin afectar el puntero de pila. El destino puede ser un operando real de 4 u 8 bytes (no el de 10 bytes).

FST ST(num): Copia ST al registro especificado.

FIST mem: Copia ST a mem. El destino debe ser un operando de 2 ó 4 bytes (no de 8 bytes) y se convierte automáticamente el número en formato temporal real a entero.

FSTP mem: Extrae una copia de ST en mem. El destino puede ser un operando de memoria de 4, 8 ó 10 bytes, donde se carga el número en punto flotante.

FSTP ST(num): Extrae ST hacia el registro especificado.

FISTP mem: Extrae una copia de ST en mem. El destino debe ser un operando de memoria de 2, 4 u 8 bytes y se convierte automáticamente el número en formato temporal real a entero.

FBSTP mem: Extrae una copia de ST en mem. El destino debe ser un operando de memoria de 10 bytes. El valor se redondea a un valor entero, si es necesario, y se convierte a BCD empaquetado.

FXCH: Intercambia ST(1) y ST.

FXCH ST(num): Intercambia ST(num) y ST.

Instrucciones de carga de constantes

Las constantes no se pueden dar como operandos y ser cargados directamente en los registros del coprocesador. Dicha constante debe estar ubicada en memoria con lo que luego se podrán usar las instrucciones arriba mencionadas. Sin embargo, hay algunas instrucciones para cargar ciertas constantes (0, 1, pi y algunas constantes logarítmicas). Esto es más rápido que cargar las constantes muy utilizadas desde la memoria.

FLDZ: Introduce el número cero en ST.
FLD1: Introduce el número uno en ST.
FLDPI: Introduce el valor de pi en ST.
FLDL2E: Introduce el valor de log(2) e en ST.
FLDL2T: Introduce el valor de log(2) 10 en ST.
FLDLG2: Introduce el valor de log(10) 2 en ST.
FLDLN2: Introduce el valor de log(e) 2 en ST.

Instrucciones de transferencia de datos de control

El área de datos del coprocesador, o parte de él, puede ser guardado en memoria y luego se puede volver a cargar. Una razón para hace esto es guardar una imagen del estado del coprocesador antes de llamar un procedimiento (subrutina) y luego restaurarlo al terminar dicho procedimiento. Otra razón es querer modificar el comportamiento del NDP almacenando ciertos datos en memoria, operar con los mismos utilizando instrucciones del 8086 y finalmente cargarlo de nuevo en el coprocesador.

Se puede transferir el área de datos del coprocesador, los registros de control, o simplemente la palabra de estado o de control.

Cada instrucción de carga tiene dos formas: La forma con espera verifica excepciones de errores numéricos no enmascarados y espera a que sean atendidos. La forma sin espera (cuyo mnemotécnico comienza con "FN") ignora excepciones sin enmascarar.

FLDCW mem2byte: Carga la palabra de control desde la memoria.
F[N]STCW mem2byte: Almacena la palabra de control en la memoria.
F[N]STSW mem2byte: Almacena la palabra de estado en la memoria.
FLENV mem14byte: Carga el entorno desde la memoria.
F[N]STENV mem14byte: Almacena el entorno en la memoria.
FRSTOR mem94byte: Restaura el estado completo del 8087.
F[N]SAVE mem94byte: Salva el estado completo del 8087.

Instrucciones aritméticas

Cuando se usan operandos de memoria con una instrucción aritmética, el mnemotécnico de la instrucción distingue entre número real y número entero. No se pueden realizar operaciones aritméticas con números BCD empaquetados en la memoria, por lo que deberá usarse FBLD para cargar dichos números desde la memoria.

FADD: Hace ST(1) más ST, ajusta el puntero de pila y pone el resultado en ST, por lo que ambos operandos se destruyen.

FADD mem: Hace ST <- ST + [mem]. En mem deberá haber un número real en punto flotante.

FIADD mem: Hace ST <- ST + [mem]. En mem deberá haber un número entero en complemento a dos.

FADD ST(num), ST: Realiza ST(num) <- ST(num) + ST.

FADD ST, ST(num): Realiza ST <- ST + ST(num).

FADDP ST(num), ST: Realiza ST(num) <- ST(num) + ST y retira el valor de ST de la pila, con lo que ambos operandos se destruyen.

FSUB: Hace ST(1) menos ST, ajusta el puntero de pila y pone el resultado en ST, por lo que ambos operandos se destruyen.

FSUB mem: Hace ST <- ST - [mem]. En mem deberá haber un número real en punto flotante.

FISUB mem: Hace ST <- ST - [mem]. En mem deberá haber un número entero en complemento a dos.

FSUB ST(num), ST: Realiza ST(num) <- ST(num) - ST.

FSUB ST, ST(num): Realiza ST <- ST - ST(num).

FSUBP ST(num), ST: Realiza ST(num) <- ST(num) - ST y retira el valor de ST de la pila, con lo que ambos operandos se destruyen.

FSUBR: Hace ST menos ST(1), ajusta el puntero de pila y pone el resultado en ST, por lo que ambos operandos se destruyen.

FSUBR mem: Hace ST <- [mem] - ST. En mem deberá haber un número real en punto flotante.

FISUBR mem: Hace ST <- [mem] - ST. En mem deberá haber un número entero en complemento a dos.

FSUBR ST(num), ST: Realiza ST(num) <- ST - ST(num).

FSUBR ST, ST(num): Realiza ST <- ST(num) - ST.

FSUBRP ST(num), ST: Realiza ST(num) <- ST - ST(num) y retira el valor de ST de la pila, con lo que ambos operandos se destruyen.

FMUL: Multiplicar el valor de ST(1) por ST, ajusta el puntero de pila y pone el resultado en ST, por lo que ambos operandos se destruyen.

FMUL mem: Hace ST <- ST * [mem]. En mem deberá haber un número real en punto flotante.

FIMUL mem: Hace ST <- ST * [mem]. En mem deberá haber un número entero en complemento a dos.

FMUL ST(num), ST: Realiza ST(num) <- ST(num) * ST.

FMUL ST, ST(num): Realiza ST <- ST * ST(num).

FMULP ST(num), ST: Realiza ST(num) <- ST(num) * ST y retira el valor de ST de la pila, con lo que ambos operandos se destruyen.

FDIV: Dividir el valor de ST(1) por ST, ajusta el puntero de pila y pone el resultado en ST, por lo que ambos operandos se destruyen.

FDIV mem: Hace ST <- ST / [mem]. En mem deberá haber un número real en punto flotante.

FIDIV mem: Hace ST <- ST / [mem]. En mem deberá haber un número entero en complemento a dos.

FDIV ST(num), ST: Realiza ST(num) <- ST(num) / ST.

FDIV ST, ST(num): Realiza ST <- ST / ST(num).

FDIVP ST(num), ST: Realiza ST(num) <- ST(num) / ST y retira el valor de ST de la pila, con lo que ambos operandos se destruyen.

FDIVR: Hace ST dividido ST(1), ajusta el puntero de pila y pone el resultado en ST, por lo que ambos operandos se destruyen.

FDIVR mem: Hace ST <- [mem] / ST. En mem deberá haber un número real en punto flotante.

FIDIVR mem: Hace ST <- [mem] / ST. En mem deberá haber un número entero en complemento a dos.

FDIVR ST(num), ST: Realiza ST(num) <- ST / ST(num).

FDIVR ST, ST(num): Realiza ST <- ST(num) / ST.

FDIVRP ST(num), ST: Realiza ST(num) <- ST / ST(num) y retira el valor de ST de la pila, con lo que ambos operandos se destruyen.

FABS: Pone el signo de ST a positivo (valor absoluto).

FCHS: Cambia el signo de ST.

FRNDINT: Redondea ST a un entero.

FSQRT: Reemplaza ST con su raíz cuadrada.

FSCALE: Suma el valor de ST(1) al exponente del valor en ST. Esto efectivamente multiplica ST por dos a la potencia contenida en ST(1). ST(1) debe ser un número entero.

FPREM: Calcula el resto parcial hallando el módulo de la división de los dos registros de la pila que están el tope. El valor de ST se divide por el de ST(1). El resto reemplaza el valor de ST. El valor de ST(1) no cambia. Como esta instrucción realiza sustracciones repetidas, puede tomar mucho tiempo si los operandos son muy diferentes en magnitud. Esta instrucción se utiliza a veces en funciones trigonométricas.

FXTRACT: Luego de esta operación, ST contiene el valor de la mantisa original y ST(1) el del exponente.

Control del flujo del programa:

El coprocesador matemático tiene algunas instrucciones que pone algunos indicadores en la palabra de estado. Luego se pueden utilizar estos bits en saltos condicionales para dirigir el flujo del programa. Como el coprocesador no tiene instrucciones de salto, se deberá transferir la palabra de estado a la memoria para que los indicadores puedan ser utilizados con las instrucciones del 8086/8088. Una forma sencilla de usar la palabra de estado con saltos condicionales consiste en mover su byte más significativo en el menos significativo de los indicadores de la CPU. Por ejemplo, se pueden usar las siguientes instrucciones:

fstsw mem16; Guardar palabra de estado en memoria.
fwait; Asegurarse que el coprocesador lo hizo.
mov ax,mem16; Mover a AX.
sahf; Almacenar byte más significativo en flags.

Palabra de estadoC3C2 C1C0 (Bits 15-8)
Indicadores 8088SFZFXAF XPFXCF (Bits 7-0)

De esta manera, C3 se escribe sobre el indicador de cero, C2 sobre el de paridad, C0 sobre el de arrastre y C1 sobre un bit indefinido, por lo que no puede utilizarse directamente con saltos condicionales, aunque se puede utilizar la instrucción TEST para verificar el estado del bit C1 en la memoria o en un registro de la CPU.

Las instrucciones de comparación afectan los indicadores C3, C2 y C0, como se puede ver a continuación.

Después de FCOMDespués de FTEST C3 C2 C0 Usar
ST > fuente ST es positivo 0 0 0 JA
ST < fuente ST es negativo 0 0 1 JB
ST = fuente ST es cero 1 0 0 JE
No comparables ST es NAN o
infinito proyectivo
1 1 1 JP

FCOM: Compara ST y ST(1).

FCOM ST(num): Compara ST y ST(num).

FCOM mem: Compara ST y mem. El operando de memoria deberá ser un número real de 4 u 8 bytes (no de 10).

FICOM mem: Compara ST y mem. El operando deberá ser un número entero de 2 ó 4 bytes (no de 8).

FTST: Compara ST y cero.

FCOMP: Compara ST y ST(1) y extrae ST fuera de la pila.

FCOMP ST(num): Compara ST y ST(num) y extrae ST fuera de la pila.

FCOMP mem: Compara ST y mem y extrae ST fuera de la pila. El operando de memoria deberá ser un número real de 4 u 8 bytes (no de 10).

FICOMP mem: Compara ST y mem y extrae ST fuera de la pila. El operando deberá ser un número entero de 2 ó 4 bytes (no de 8).

FCOMPP: Compara ST y ST(1) y extrae dos elementos de la pila, perdiéndose ambos operandos.

FXAM: Pone el valor de los indicadores según el tipo de número en ST. La instrucción se utiliza para identificar y manejar valores especiales como infinito, cero, números no normalizados, NAN (Not a Number), etc. Ciertas operaciones matemáticas son capaces de producir estos números especiales. Una descripción de ellos va más allá del alcance de este apunte.

Instrucciones trascendentales

F2XM1: Realiza ST <- 2 ^ ST - 1. El valor previo de ST debe estar entre 0 y 0,5.

FYL2X: Calcula ST(1) * log(2) ST. El puntero de pila se actualiza y luego se deja el resultado en ST, por lo que ambos operandos se destruyen.

FYL2XP1: Calcula ST(1) * log(2) (ST + 1). El puntero de pila se actualiza y luego se deja el resultado en ST, por lo que ambos operandos se destruyen. El valor absoluto del valor previo de ST debe estar entre 0 y la raíz cuadrada de 2 dividido 2. FPTAN: Calcula la tangente del valor en ST. El resultado es una razón Y/X, donde X reemplaza el valor anterior de ST y Y se introduce en la pila así que, después de la instrucción, ST contiene Y y ST(1) contiene X. El valor previo de ST debe ser un número positivo menor que pi/4. El resultado de esta instrucción se puede utilizar para calcular otras funciones trigonométricas, incluyendo seno y coseno.

FPATAN: Calcula el arcotangente de la razón Y/X, donde X está en ST e Y está en ST(1). ST es extraído de la pila, dejando el resultado en ST, por lo que ambos operandos se destruyen. El valor de Y debe ser menor que el de X y ambos deben ser positivos. El resultado de esta instrucción se puede usar para calcular otras funciones trigonométricas inversas, incluyendo arcoseno y arcocoseno.

Control del coprocesador

F[N]INIT: Inicializa el coprocesador y restaura todas las condiciones iniciales en las palabras de control y de estado. Es una buena idea utilizar esta instrucción al principio y al final del programa. Poniéndolo al principio asegura que los valores en los registros puestos por programas que se ejecutaron antes no afecten al programa que se comienza a ejecutar. Poniéndolo al final hará que no se afecten los programas que corran después.

F[N]CLEX: Pone a cero los indicadores de excepción y el indicador de ocupado de la palabra de estado. También limpia el indicador de pedido de interrupción del 8087.

FINCSTP: Suma uno al puntero de pila en la palabra de estado. No se afecta ningún registro.

FDECSTP: Resta uno al puntero de pila en la palabra de estado. No se afecta ningún registro.

FREE ST(num): Marca el registro especificado como vacío.

FNOP: Copia ST a sí mismo tomando tiempo de procesamiento sin tener ningún efecto en registros o memoria.

Nedstat Counter