El coprocesador matemático

Bueno, este documento es una breve iniciación al funcionamiento del coprocesador matemático en los sistemas PC y, más concretamente, a la arquitectura de los procesadores PENTIUM, puesto que hoy por hoy es lo más utilizado. No es mi intencion dar una explicacion completa de su arquitectura interior, simplemente trato de arrojar un poco de luz donde no la hay, porque cuando decidí meterme con el coprocesador matemático, no encontre apenas documentación y la poca (por no decir nula) estaba en inglés.

¿Merece la pena usar el copro?

Esta pregunta es algo ambigua, porque depende mucho de la aplicacion en sí. Si lo que queremos realizar son calculos intensivos en 3D la respuesta es sí. Esto depende mucho por la incapacidad que tiene el coprocesador de intercambiar datos con los registros de proposito general, lo que obliga a costosas descargas en memoria con conversion a entero y que provoca que, por ejemplo, un calculo en el que tengamos que utilizar directamente el resultado de la operacion como un puntero a una zona de memoria, o como valor para introducir en una zona de memoria, sea algo lento.

Si por ejemplo lo que queremos es realizar un calculo sobre unos datos dados (por ejemplo una multiplicacion de matrices) si que merece la pena su uso.

Estructura interna

El coprocesador trabaja internamente sólo en formato real, por lo que cualquier carga en los registros de coprocesador provocará que dicho valor sea convertido a coma flotante.

Sus registros están estructurados en forma de pila y se accede a ellos por el numero de entrada que ocupan en la pila.

Los registros son R(0) hasta R(7), en total ocho registros de 80bits, como han de ser manejados en formato de pila, el coprocesador tiene un puntero de control de pila llamado St, Toda interacción que tengamos que hacer con los registros del coprocesador se realiza a traves del puntero de pila St, donde el último valor introducido es St o St(0) y si hubieramos rellenado todos los registros el ultimo seria St(7)... ¿ok? Por ejemplo:

  1. Cargar en copro dato (1345)
  2. ahora St(0)=1345
  3. Cargar en copro dato (5431)
  4. ahora St(0)=5431 y St(1)=1345

Tipos de datos

El coprocesador puede obtener y escribir datos en memoria de los siguientes tipos.

Entero Words(16bits),Dword(32 bits),Qwords(64 bits)
Real Words(16 bits),Dword(32 bits),Qwords(64 bits ),Twords(80 bits)

Un poco de materia...

Bien, ya hemos visto como ordena el coprocesador sus registros, ahora vamos a ver alguna operacion sencilla.

Todos los ejemplos que aquí escriba estarán enteramente en ensamblador, puesto que si lo que queremos es velocidad de poco nos sirve la math387.lib del C.

Sumar un entero yun real
            .386
            .Model  Flat
            .Stack  800h

            .DATA

Numero1     dd      25        ; Numero en formato entero
Numero2     dd      1.25      ; Numero en formato real
                              ; (¡Ojo! este numero solo puede ser
                              ; accedido correctamente por el copro
                              ; a no ser que nos hagamos una rutina
                              ; conversora de formato de FPU a entero
                              ; cosa que no merece la pena, porque
                              ; es mas facil cargarlo y convertirlo
                              ; con el copro, y mas rapido)

Resul       dd      ?         ; Variable para el resultado
Desca       dd      ?         ; Variable para correccion de pila

            .CODE

Start:
            fild    Dword Ptr ds:[Numero1] ;Cargar en el copro la
                                           ;variable Numero1 indicando
                                           ;que es un entero
            fld     Dword Ptr ds:[Numero2] ;Idem pero es un real
            fadd    St(0),St(1)            ;Sumarlos
                                               ;St(0)=St(0)+St(1)
            fstp    Dword Pt  ds:[Resul]   ;Descargar el resultado.
            fstp    Dword Ptr ds:[Desca]   ;Descartar el otro valor.
        
            mov     eax,4c00h
            int     21h
            End     Start

Como hemos visto al funcionar a base de pila siempre tenemos que dejarla tal y como se encontraba al iniciarse nuestra rutina, de ahi la operacion de popeo con la variable Desca, aunque esto puede realizarse más rapido, siempre teniendo en cuenta varias cosas.

En vez de usar fadd podemos usar faddp que automaticamente suma St(0) y St(1) y elimina automáticamente el operando sobrante de la pila quedando en St(0) el resultado listo para ser popeado.

¡Ojo, las instrucciones de cálculo con la particula "p" ej: faddp o fdivp, sólo trabajan con las posiciones de pila St(0) y St(1)!

También podríamos haber usado en vez del fstp a Descal un ffree St(1), esta instrucción automáticamente libera ese componente de la pila, pero sólo es efectiva para dejar la pila tal y como estaba cuando el operando a liberar ocupa la última posicion en la pila (esto es, el primero introducido).

Start:
            fild    Dword Ptr ds:[Numero1]  ;Cargar en el copro la
                                            ;variable Numero1 indicando
                                            ;que es un entero
            fld     Dword Ptr ds:[Numero2]  ;Idem pero es un real
            faddp                           ;Sumarlos y popear el
                                            ;operando sobrante.
                                            ;St(0)=St(0)+St(1)
                                            ;pop st(1) > nul
                                            ;Solo opera con estos
                                            ;punteros de pila!!
            fstp    Dword Ptr ds:[Resul]    ;Descargar el resultado.
            mov     eax,4c00h
            int     21h

            End     Start

o bien...

            .CODE

Start:
            fild    Dword Ptr ds:[Numero1]  ;Cargar en el copro
                                            ;la variable Numero1
                                            ;indicandole que es un entero
            fld     Dword Ptr ds:[Numero2]  ;Idem pero es un real
            fadd    St(0),St(1)             ;Sumarlos
                                            ;St(0)=St(0)+St(1)
            fstp    Dword Ptr ds:[Resul]    ;Descargar el resultado.
                                            ;pop St(0) > Resul
            ffree   St(0)                   ;Descartar el otro valor.
                                            ;Free St(0) (stack cleared)
            mov     eax,4c00h
            int     21h
            End     Start

Como habreis podido observar liberamos con ffree St(0) esto es por lo anteriormente comentado. Al liberar el resultado de la pila St(1) pasa a ser St(0), St(2)-St(1), etc. Debido a su estructura en forma de pila.

Instrucciones

Estas son las instrucciones mas comunes, he omitido algunas, por tratarse por ejemplo de aritmetica BCD (cosa poco comun en el mundo del tiempo-real) pero para los interesados, Intel tiene a su disposicion unos archivos en formato de Acrobat Reader con la lista y la organizacion interna de la FPU. Los ciclos de reloj de las instrucciones, son los declarados por Intel para el Pentium basico, esto puede verse alterado en las CPU's MMX, PRO y Pentium II. No voy a comentar las instrucciones porque en este momento carezco del tiempo necesario para realizar una documentación sobre las instrucciones de manera seria, pero de todas formas solo hay que ser un poco despierto para con solo los nombres hacerse una idea basica del funcionamiento de cada instrucción. Cualquier instruccion de cálculo ( fadd, fsub, etc) toman por defecto si no se le especifica otros operandos St(0) y St(1).

Leyenda: rm=Modo Real, vm=Modo Virtual, pm=Modo Protegido

Instrucción Ejemplo Ciclos de reloj
FABS fabs 1
FADD [reg,reg] fadd 3, 1
FADD memreal fadd shortreal 3, 1
FADDP reg,ST faddp st(6),st 3, 1
FIADD memint fiadd int16 7, 4
FCHS fchs 1
FCLEX fclex 9+
FNCLEX fnclex 9
FCOM fcom 4, 1
FCOMP fcomp 4, 1
FCOMPP fcompp 4, 1
FICOM memint ficom double 8, 4
FICOMP memint ficomp darray[di] 8, 4
FCOS fcos 18-124
FDECSTP fdecstp 1
FDIV [reg,reg] fdiv st(5),st 39
FDIV memreal fdiv longreal 39
FDIVP reg,ST fdivp st(6),st 39
FIDIV memint fidiv warray[di] 42
FDIVR [reg,reg] fdivr st(5),st 39
FDIVR memreal fdivr longreal 39
FDIVRP reg,ST fdivrp st(6),st 39
FIDIVR memint fidivr warray[di] 42
FFREE ST(i) ffree st(3) 1
FILD memint fild quads[si] 3, 1
FINCSTP fincstp 1
FINIT finit 16
FNINIT fninit 12
FIST memint fist doubles[8] 6
FISTP memint fistp longint 6
FLD reg fld st(3) 1
FLD mem32real fld longreal 1
FLD mem64real

 

1
FLD mem80real

 

3
FLD1 fld1 2
FLDZ fldz 2
FLDPI fldpi 5,3
FLDL2E fldl2e 5, 3
FLDL2T fldl2t 5, 2
FLDLG2 fldlg2 5, 3
FLDLN2 fldln2 5, 3
FLDCW mem16 fldcw ctrlword 7
FLDENV mem fldenv [bp+10] 37, 16-bit pm=32,
32-bit pm=33
FMUL [reg,reg] fmul st(5),st 3, 1
FMULP reg,ST fmulp st(6),st 3, 1
FIMUL memint fimul warray[di] 7, 4
FNOP fnop 1
FPATAN fpatan 17-173
FPREM fprem 16-64
FPREM1 fprem1 20-70
FPTAN fptan 17-173
FRNDINT frndint 9-20
FRSTOR mem frstor [bp-94] 16-bit rm or vm=75;
32-bit rm or vm=95;
pm=70
FSAVE mem fsave [bp-94] 16-bit rm or vm=127+;
32-bit rm or vm=151+;
pm=124+
FNSAVE mem fnsave [bp-94] 16-bit rm or vm=127;
32-bit rm or vm=151;
pm=124
FSCALE fscale 20-31
FSIN fsin 16-126
FSINCOS fsincos 17-137
FSQRT fsqrt 70
FST reg fst st 1
FST memreal fst longs[bx] 2
FSTP reg fstp st(3) 1
FSTP mem32real fstp longreal 2
FSTP mem64real

 

2
FSTP mem80real

 

3
FSTCW mem16 fstcw ctrlword 2+
FNSTCW mem16 fnstcw ctrlword 2
FSTENV mem fstenv [bp-14] 16-bit rm or vm=50+;
32-bit rm or vm=48+;
16-bit pm=49+;
32-bit pm=50+
FNSTENV mem fnstenv [bp-14] 16-bit rm or vm=50;
32-bit rm or vm=48;
16-bit pm=49;
32-bit pm=50
FSTSW mem16 fstsw statword 2+
FSTSW AX fstsw ax 2+
FNSTSW mem16 fnstsw statword 2
FNSTSW AX fnstsw ax 2
FSUB [reg,reg] fsub st,st(2) 3, 1
FSUB memreal fsub longreal 3, 1
FSUBP reg,ST fsubp st(6),st 3, 1
FISUB memint fisub double 7, 4
FSUBR [reg,reg] fsubr st,st(2) 3, 1
FSUBR memreal fsubr longreal 3, 1
FSUBRP reg,ST fsubrp st(6),st 3, 1
FISUBR memint fisubr double 7, 4
FTST ftst 4, 1
FUCOM [reg] fucom st(2) 4, 1
FUCOMP [reg] fucomp st(7) 4, 1
FUCOMPP fucompp 4, 1
FWAIT fwait 1-3
FXAM fxam 21
FXCH [reg] fxchg st(3) 1
FXTRACT fxtract 13
FYL2X fyl2x 22-111
FYL2XP1 fyl2xp1 22-103

Hints, o cómo acelerar

Lo primero es que, como ya hemos visto con los ciclos de las instrucciones, el volcado y carga de memoria es lento, con lo cual, la primera regla, es realizar las menores cargas y volcados posibles, aprovechando al maximo las posiciones de la pila para cuantas más operaciones mejor.

Por ejemplo, hemos de multiplicar un array por 5...

Multiplicar un array por 5
            .DATA

Array       dd      200 dup(?)              ;Array con datos.. :D
Value       dd      5

            .CODE

            fild    Dword Ptr ds:[Value]
            mov     ecx,200                 ;200 datos a multiplicar
            mov     edi,offset Array
Bucle:
            fild    Dword Ptr ds:[edi]      ;fild suponiendo que son
                                            ;enteros..
            fmul                            ;Multiplica St(0),St(1)
            fistp   Dword Ptr ds:[edi]      ;descargamos el operando
            add     edi,4
            dec     ecx
            jnz     Bucle
            ffree   St(0)                   ;Liberar el dato "5" :)

o bien, para ahorrarnos el ffree... :)

            fild    Dword Ptr ds:[Value]
            mov     ecx,199                ;200 datos a multiplicar
            mov     edi,offset Array
Bucle:
            fild    Dword Ptr ds:[edi]     ;fild suponiendo que son
                                           ;enteros..
            fmul                           ;Multiplica St(0),St(1)
            fistp   Dword Ptr ds:[edi]     ;descargamos el operando
            add     edi,4
            dec     ecx
            jnz     Bucle
            fmulp                          ;St(0)=St(0)*St(1)
                                           ;pop St(1) > nul
            fistp   Dword Ptr ds:[edi]

Esto en realidad es un ejemplo tonto, pero como podemos observar la enseñanza del mismo es directamente aplicable a muchas rutinas matemáticas de ámbito 3D.

Otra cosa a tener en cuenta es que, si las cargas y descargas ya de por sí son lentas (hay que tener en cuenta cache hits en las cargas) es más lento todavía con la inclusión de la particula "i", ya que la FPU ha de convertir ese dato entero a real, esto provoca que siempre perdamos unos pocos ciclos en la operación. Aun así, lo expuesto arriba ya de por sí es más rápido que lo mismo escrito en código de procesador (osea con imuls,etc).

Esto es fácilmente solucionable, si nuestra aplicacion necesita realizar diversos cálculos matemáticos con un array de vectores, con lo cual lo realmente óptimo es tener almacenados esos datos en real, operar con ellos, y en la ultima operación a realizar para su utilización (una proyección 3D a 2D, por ejemplo) aprovechar para hacer la conversión a entero.

También hay otras optimizaciones, hay algunas instrucciones pairebles, concretamente, el Fmul con el Fxchg, pero de esto hablaré en una sucesiva actualizacion de documento (¡maldito tiempo!).

Ejemplo Práctico: Una rutina de proyección 3D a 2D

Los arrays son de Dwords en formato X,Y,Z con 12 bytes cada vector. El de entrada es real, el de salida sera entero.

Proyección 3D a 2D
;In	Ecx=Number of vertex
;	Edi=pointer to destination
;	Esi=pointer of 3D vertex
;
;Out	Action Performed!
;---------------------------------------

ProyCords   Proc    Near
            fild    ds:[Ycenter]            ;World Centering on screen
            fild    ds:[Xcenter]            ;Idem.
            fild    ds:[Pvi]                ;Focal Distance.

c3d2d:
            fld     ds:[esi+8]              ;Get "Z"
            fld1                            ;Load number "1"
            fdivrp                          ;Calc Inverse and pop
                                            ;the not valuable operand
            fld     Dword Ptr ds:[esi+4]    ;Get "Y"
            fmul    st,st(2)                ;Multiply Y with Focal Distance
            fmul    st,st(1)                ;Multiply with the inverse
                                            ;of the "Z"
            fadd    st,st(4)                ;Add Y Screen centering
            fistp   Dword Ptr ds:[edi+4]    ;Store final value
            fld     Dword Ptr ds:[esi]      ;Get "X"
            fmul    st,st(2)                ;Multiply X with Focal Distance
            fmul    st,st(1)                ;Multiply with the inverse
                                            ;of the "Z"
            fadd    st,st(3)                ;Add X Screen centering
            fistp   Dword Ptr ds:[edi]      ;Store final value
            fstp    ds:[Dummy]              ;Free the Z inverse
            add     esi,12                  ;Increment pointers
            add     edi,12                  ;Increment pointers
            dec     ecx                     ;decrement counter
            jnz     c3d2d                   ;if not 0 go to other vertex
            fistp   ds:[Dummy]
            fistp   ds:[Dummy]
            fistp   ds:[Dummy]              ;Free all
            ret
ProyCords   Endp

Como se puede observar en esta rutina, hacemos uso de los dos ejemplos de optimizacion expuestos anteriormente. Tomar los datos, desde una base de datos de reales, utilizar la conversión 3D a 2D para pasarlos a enteros (para poder realizar luego el pintado) y utilizar al máximo de lo que se pueda las posiciones de la pila para guardar datos que han de ser reutilizados. Otra optimización es el uso de inversos, para lo cual primero calculamos ZI=1/Z y luego para obtener X'=X*PVI/Z realizamos la siguiente operación: X'=(X*PVI)*ZI.

Desde luego esto es más rápido que dos divisiones pero, de todas formas, aunque se realizara con dos divisiones de copro, el resultado sigue siendo todavía más rápido que su misma ecuación programada con el procesador.

Además, los inversos se pueden utilizar hasta en los sitios mas insospechados. Por ejemplo, en nuestro sistema de sonido, en el player de Sb hay una tabla de inversos para eliminar DIV's en la rutina de mezcla.

Cuidado con...

En primer lugar, mucho cuidado con la pila, cualquier desestabilizacion de la pila puede provocar resultados insospechados y hasta, en muchos casos, el cuelgue total del computador.

En segundo lugar, con los compiladores. Ignoro si hay algun compilador perfecto para el inline en asm, porque realmente yo no uso ninguno, siempre programo en 100% ensamblador, pero he visto casos como el de un chaval que una noche a través del IRC me comento que un bucle con MUL's y otro con FMUL's le funcionaban a la misma velocidad. Me paso el exe, y me di cuenta que el WATCOM C le habia generado en vez de FMUL's, FMULP's, con lo cual siempre popeaba datos y ocasionaba una excepción de pila, lo que hacia que el codigo de copro se ejecutara más lento. Las excepciones de pila por arriba, esto es por St(0) son controlables, pero por abajo (St(7)) no lo son tanto.

No sé si esto es un bug declarado del compilador, pero tambien he oido que existen algunos problemas con la conversion de real a entero de la citada Math387 del Watcom C, por lo cual aconsejo, que después de haber escrito la rutina, coger un debugger y asegurarse de que el codigo generado por el compilador es exacto.

Nosotros aqui en TLOTB trabajamos tanto con TASM como con NASM y con ninguno de los dos hemos tenido ningun problema con las instrucciones y sintaxis de copro, con lo cual tambien aconsejo el ensamblar las rutinas con alguno de estos dos ensambladores y despues enlazarlas con el programa principal.

¿Que hay de verdad sobre las transferencias con copro?

Bien, esto es un tema peliagudo, y lo evaluaremos en dos casos particulares.

  • Memoria->Memoria.

    En este caso se obtiene una ligera mejoria por tres razones. La primera es porque el procesador internamente esta orientado al trabajo con Qwords (si no, mirar la instruccion MOVQ del MMX).

    La segunda es el conexionado del procesador a la memoria del sistema, cuya transmisión optima es 64bits (mejor si disponemos de modulos de memoria DIMM). La tercera, el alineamiento a 64bits es lo mas óptimo en una CPU Pentium. Aquí, en transferencia, hay que tener cuidado con los NaN.

  • Memoria->Bus (SVGA,etc)

    En este caso depende mucho del chipset utilizado en la placa base. Despues de pruebas en varios equipos, se llega a la conclusion de que, aunque gastemos transferencia por copro, en el peor de los casos tarda lo mismo que con Dwords (osea un MOVSD) y en el mejor de los casos se gana en torno a un 5% o 10%. Esto oscila mucho dependiendo de la saturacion del BUS, de la SVGA y su velocidad para tragar datos,etc. Con lo cual, de todas formas es aconsejable su utilizacion, o al menos, probar que tal va! :)

Más Hints

Para borrar una zona de memoria (por ejemplo una pantalla virtual) el copro bate records.

Alineamiento... hacer la siguiente prueba...

  1. Tomar una direccion alineada por ejemplo a Qword
  2. esperar al retrazo
  3. colocar el color de borde a verde por ejemplo
  4. volcar la pantalla.
  5. colocar el color de borde a negro por ejemplo
  6. goto al principio

Apuntar lo que tarda y luego hacer lo mismo con una direccion sin alinear.. ¡veréis la grandiosa diferencia! :)

Palabras de control y estado de coprocesador y registros de flag

Aqui esta la especificación de bits de las palabras de estado y de control, Hay que tener en cuenta que esta documentación la he podido conseguir sólo del 387, y que hay bastantes diferencias con el 287, asi que podría ser que en los actuales cambie alguna cosa.

PALABRA DE CONTROL

Bit Nombre Descripción
15-13 -- Reservados en el 387
12 -- Reservados en el 387; en el 287 era control de infinito.
11-10 RC Rounding Control:
  • 00 = redondear al mas cercano; si es XXX.5 entonces ir al numero par más cercano.
  • 01 = Redondear siempre por abajo (2.1 -> 2; -2.1 -> -3)
  • 10 = Redondear siempre por arriba (2.1 -> 3; -2.1 -> -2)
  • 11 = Quitar decimales (2.1 -> 2; -2.1 -> -2)
9-8 PC Precision Control:
  • 00 = 24 bits (single precision)
  • 01 = reservado en el 387
  • 10 = 53 bits (double precision)
  • 11 = 64 bits (extended precision)
7-6 -- Reservados en el 387
5 PM Máscara de excepción de Precisión
4 UM Máscara de excepción de Underflow
3 OM Máscara de excepción de Overflow
2 ZM Máscara de excepción de Zero Divide
1 DM Máscara de excepción de Denormalized Operand
9 IM Máscara de excepción de Invalid Operation

PALABRA DE ESTADO

Bit Nombre Descripción
15 B Flag de Busy
14 C3(Z) Parte del Condition Code.
13-11 TOP Puntero de pila (Top of Stack)
10 C2(C) Parte del Condition Code.
9 C1(A) Parte del Condition Code.
8 C0(S) Parte del Condition Code.
7 ES Error Summary flag
6 SF Stack Flag
5 PE Excepción de Precision
4 UE Excepción de Underflow
3 OE Excepción de Overflow
2 ZE Excepción de Zero Divide
1 DE Excepción de Denormalized Operand
0 IE Excepción de Invalid Operation

Los Condition Codes son bastante complicados de interpretar, ya que su interpretación depende de la instrucción. Baste decir que tras la comparación (FCOM y familia) resulta lo siguiente:

C3 C2 C0 Significado
0 0 0 TOP > Operand
0 0 1 TOP < Operand
1 0 0 TOP = Operand
1 1 1 Unordered

C1 es para la comparacion el flag de Zero, aunque no sé si eso significa que debe ser interpretado igual que el flag de Zero del registro Flags.

Si hay un Overflow o Underflow de pila, los bits IE y SF se pondrán a 1, y el flag C1 distinguira entre overflow (1) y underflow (0).

Denormals

(Esto no está en la documentacion de intel, y puedo equivocarme en algo).

La representación en coma flotante consiste en un bit de signo, un exponente y una mantisa, como sabéis. Para permitir exponentes negativos, en vez de almacenar el exponente en formato de complemento a dos, se almacena sumándole una cantidad llamada Bias, que es 127 para el Single, 1023 para el Double y 16383 para el Extended.

Los numeros Single y Double suponen un 1,... implícito antes de empezar la mantisa, pero en el Extended no es implícito, sino que lo necesita explicitamente.

Parece una tontería, pero en los Single y Double, ¿cómo se almacena un cero? Porque el siguiente caso, que se corresponde a todos los bits a cero (pongamos que de Single):

  • Exponente = 0
  • Mantisa = (1,)000000000000...
  • Signo = 0

significaria 1,0000... * 2^-127, pero no cero (aunque esté cerca).

La solución que se ha adoptado es considerar como caso especial el de Exponente = 0, en cuyo ese caso en vez de haber un 1 implícito en la mantisa hay un 0 implícito.

Asi:

  • Exponente = 0
  • Mantisa = (0,)0001001110001000
  • Signo = 0

se corresponde a 0,0001001110001000 * 2^-127, o lo que es igual, 1,001110001000 * 2^-131.

Ademas, de esa manera el cero puede ser representado, ya que 0,00000000000... * 2^-127 = 0.

A toda la familia de numeros con Exponente = 0, excepto el cero, se les llama Denormals. En el libro de Knuth (Seminumerical Algorithms) menciona la utilidad de los Denormals para no perder tanta precisión en ciertos cálculos.

El caso de los Extended no es diferente, salvo que el 1 explicito puede dejar de estar ahi cuando el exponente es cero (en el resto de numeros, el 1 era obligatorio que estuviera).

Ahora una nota del cuadernillo:

"FLD single/double cuando el operando es un Denormal convierte el numero a Extended precision y lanza la excepcion de Denormalized Operand. Al cargar un Signaling NaN, FLD single/double lanza una excepcion de Invalid Operand."

NaN significa Not a Number, aunque no sé qué formato tienen esos.

La tabla de excepciones dice lo siguiente:

Excepción Causa Acción por defecto (si la excepción está enmascarada)
Invalid Op. Operación sobre un Signaling NaN, formato no soportado, operacion indeterminada (0*infinito, 0/0, infinito-infinito, etc), o underflow/overflow de pila El resultado es un Quiet NaN, un entero indefinido o un BCD indefinido.
Denormalized Op. Al menos uno de los operandos es un Denormal, o sea, tiene el menor exponente pero una mantisa no-cero. Continua el proceso normal.
Zero Divisor El divisor es cero mientras el dividendo es un numero no cero y no infinito. El resultado es Infinito.
Overflow El resultado es demasiado grande para caber en el formato especificado. El resultado es el mayor numero posible o infinito.
Underflow El resultado es no-cero pero es demasiado pequeño para caber en el formato especificado, y si está enmascarada la excepción de Underflow, la denormalizacion causa pérdida de precisión. El resultado es un denormal o cero.
Resultado Inexacto (Precisión) El resultado EXACTO no es representable en el formato especificado (p.ej. 1/3); el resultado es redondeado de acuerdo con el modo de redondeo. Continua el proceso normal.

Para terminar.... de empezar.... :))

Lo aconsejable, es realizar al principio del programa un finit para inicializar el copro, despues descargar la palabra de control y enmascarar las excepciones y luego volver a cargarlas, con lo cual, reduciremos el tiempo de proceso de las instrucciones que puedan causar una excepcion.

Espero que esto os ayude en algo, la verdad para mi es suficiente para lo que es el manejo habitual de copro.

Agradecimientos y Saludos

Ummm.... a.....

  • Cranky/TloTb.. por su apoyo linguistico, y por documentacion.
  • Unreal/Pulse.. por incitarme a escribir esto.
  • A los habituales en #demos,#coders y en es.comp.demos

Saludos especiales para Jcab/Iguana por la charla que tuvimos sobre los Denormals, los NaN y las transferencias de copro.

Todas las marcas registradas son propiedad de sus respectivos dueños, etc.. :)

Si alguien quiere una mejor revisión de este documento, o simplemente quiere consultarme algo o bien mandarme una amenaza para que deje de escribir...

Podeis dar conmigo en [email protected] o a traves de http://www.tlotb.com.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP