UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Programación Avanzada en C Rutina matemáticas. Trigonométricas. Para desarrollar el algoritmo, consideremos la relación: Sen(-x) = -sen(x) lo cual permite mediante un cambio de variable y signo efectuar cálculos sólo para x>=0. La variable x se expresa en radianes, y es periódica. Se muestra la gráfica para un período. plot(sin(x),x=0..2*Pi); Si efectuamos el cambio de variable, w = x/2*Pi, tendremos: plot(sin(2*Pi*w),w=0..1); cuya gráfica se ilustra a continuación: Reducción al primer período: Para considerar la naturaleza periódica de la función, podemos considerar el cambio de variables: Z = w – floor(w), cuya gráfica se obtiene con plot(w - floor(w), w=0..5); Prof. Leopoldo Silva Bijit. 18-08-2003 30 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Programación Avanzada en C Que mapea los diferentes intervalos de w entre i e i+1 en el intervalo de z entre 0 y 1. La función floor(w) trunca el número real al entero menor; en el caso de reales positivos, equivale al truncamiento del número. Por ejemplo: floor(1.5) = 1.0 Después de este cambio de variables, los valores del argumento estarán acotados. De esta forma cuando se calcule con valores reales elevados, éstos se reducen a valores entre 0 y 4, y no se producirán errores cuando se calculen las potencias del argumento al evaluar la serie. Si efectuamos: m= 4*(w-floor(w)) las variaciones de m serán en el intervalo entre 0 y 4, cuando w cambia entre cualquier inicio de un período hasta el final de ese período. Entonces para todos los reales positivos(representables) de w, se puede calcular en el primer período, para valores de m entre 0 y 4: plot( sin(2*Pi*m/4 ),m=0..4); Reducción al primer cuadrante: Para 4 > m > 2 se tiene que f(m) = - f(m-2) y si se efectúa m=m-2, se tendrá que 0<m<2. Ahora m está restringido a tomar valores entre 0 y 2. Para 2> m > 1 se tiene f(m) = f(2-m) y si se efectúa m= 2-m, se tendrá que 0 < m < 1, lo cual reduce los cálculos al primer cuadrante. El intervalo donde se calculará el polinomio de aproximación se muestra en la siguiente gráfica: Prof. Leopoldo Silva Bijit. 18-08-2003 31 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Programación Avanzada en C plot( sin(2*Pi*m/4 ),m=0..1); Entonces puede describirse el siguiente algoritmo: signo = 1.0; /*describe signo positivo */ if(x < 0.0) { x = -x; signo = -signo; } /*Desde ahora sólo argumentos positivos */ x /= TWO_PI; /* 1 radian = 180/Pi Grados. Desde ahora: Inf > x > 0 */ x = 4.0 * (x - floor(x)); /* Reduce al primer período. Desde ahora 4 >= x >= 0 */ if(x > 2.0) { x -= 2.0; signo = -signo;} /* 2 >= x >=0 */ if( x > 1.0) x = 2.0 - x; /* Reduce al primer cuadrante. 1>= x >=0 */ Puede compararse la aproximación por series de potencia(de dos y tres términos) con el polinomio de Pade, mediante: plot([x-x^3/6,x-x^3/6+x^5/120, pade(sin(x),x=0,[9,6])], x=0.7..1.7,y= 0.65..1,color=[red,blue,black], style=[point,line,point]); Prof. Leopoldo Silva Bijit. 18-08-2003 32 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Programación Avanzada en C Cuando m varía entre 0 y 1, el x de la gráfica anterior varía entre 0 y 2*Pi/4 = 1,571 Se muestra a partir de la ordenada 0,65 para ampliar la zona en que las aproximaciones difieren. Es preciso calcular polinomios, math.h Si por ejemplo se desea calcular: puede emplearse la función estándar poly, descrita en p(x) = d[4]*(x**4)+d[3]*(x**3)+d[2]*(x**2)+d[1]*(x)+d[0] puede describirse por: (((d[4]*x + d[3] )*x + d[2] )*x + d[1] )*x +d[0] Con algoritmo: poli=d[n]; i=n; while (i >0) {i--; poli = poli* x + d[ i-1]}; Debido a que los polinomios son de potencias pares en el denominador, se efectúa el reemplazo x por x*x. Y para obtener potencias impares en el numerador se multiplica el polinomio del numerador por x. El algoritmo completo es: Prof. Leopoldo Silva Bijit. 18-08-2003 33 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Programación Avanzada en C #include <math.h> /*Calcula para n=4 el polinomio: d[4]*(x**4)+d[3]*(x**3)+d[2]*(x**2)+d[1]*(x)+d[0] */ double eval_poly(register double x, const double *d, int n) { int i; register double res; res = d[i = n]; while ( i ) res = x * res + d[--i]; return res; } #define PI 3.14159265358979 #define TWO_PI 6.28318530717958 double seno(double x) { static const double coeff_a[] = { 207823.68416961012, -76586.415638846949, 7064.1360814006881, -237.85932457812158, 2.8078274176220686 }; static const double coeff_b[] = { 132304.66650864931, 5651.6867953169177, 108.99981103712905, 1.0 }; register double signo, x2; signo = 1.0; if(x < 0.0) { x = -x; signo = -signo; } /*Solo argumentos positivos */ x /= TWO_PI; x = 4.0 * (x - floor(x)); if(x > 2.0) { x -= 2.0; signo = -signo;} if( x > 1.0) x = 2.0 - x; x2 = x * x; return signo * x * eval_poly(x2, coeff_a, 4) / eval_poly(x2, coeff_b, 3); } Empleando Mapple puede obtenerse el polinomio de Pade, que aproxima a la función seno. with(numapprox): Ø pade(sin(x), x=0, [9,6]); 1768969 9 36317 7 80231 5 8234 3 (-------------------- x - -------------- x + ------------- x - -------- x + x ) / 4763930371200 472612140 14321580 55083 631 2 3799 4 911 6 (1 + --------- x + ------------- x + --------------- x ) 36722 28643160 1890448560 El siguiente comando dibuja el polinomio: plot(pade(sin(x),x=0,[9,6]),x=0..10); Prof. Leopoldo Silva Bijit. 18-08-2003 34 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Programación Avanzada en C Se aprecia que para x>6 la aproximación de la función seno no es buena. Se requiere modificar el argumento de la función, de acuerdo al algoritmo: a:=pade(sin(2*Pi*x/4), x=0, [9,6]); evalf(denom(a)/10^11,17); Calcula el denominador, dividido por 10^11, con 17 cifras. 24391.323500544000+1034.1371819921864*x^2+19.695328959656098*x^4+.176566431 95797582*x^6 evalf(expand(numer(a)/10^11),17); Calcula el numerador, dividido por 10^11, con 17 cifras. 38313.801360320554*x-14131.500385136448*x^3+1306.7304862998132*x^544.226197226558042*x^7+.52731372638787005*x^9 Los valores de los coeficientes son los que se emplean en la función. La gráfica del polinomio es la zona donde será evaluado, se muestra a continuación: plot(pade(sin(2*Pi*x/4),x=0,[9,6]), x=0..4); Prof. Leopoldo Silva Bijit. 18-08-2003 35 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Programación Avanzada en C Manipulación de flotantes. La función floor está basada en el truncamiento de la parte fraccionaria del número real. Si se tiene: Double d, t ; Entonces t = (double)(long)(d); es el número truncado, con parte fraccionaria igual a cero. Primero el molde (long) transforma d a un entero, luego el molde o cast (double) transforma ese entero a doble. Si la cantidad de cifras enteras de un double, no pueden ser representadas en un entero largo, la expresión será errónea. Por ejemplo si el entero largo tiene 32 bits, si las cifras enteras del doble exceden a 231 -1 se tendrá error. Un doble IEEE 754 ocupa 64 bits, con un long double de 80 bits, no hay problemas en el truncamiento. Un double de 64 bits tiene el rango: 1.7 * (10**-308) to 1.7 * (10**+308) . Un long double de 80 bits tiene el rango: 3.4 * (10**-4932) to 1.1 * (10**+4932) . Double floor( double x) { double i; i = (double)(long double)(x); if(i > x) return i - 1.0; return i; } Luego pueden derivarse el resto de las funciones trigonométricas. La función coseno, se calcula #define PImedio 1.570796326794895 double coseno(double x) { return seno(x + PImedio); } La función tangente, se deriva de su definición: double tangente(double x) { return seno(x)/coseno(x); } El valor absoluto de un doble, se calcula según: double fabs(double d) { if(d < 0.0) return -d; else return d; } Prof. Leopoldo Silva Bijit. 18-08-2003 36 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Programación Avanzada en C Acceso a los bits de un número. En ocasiones resulta conveniente tener acceso a las representaciones internas de los números. Los programas de este tipo deben considerar el ordenamiento de los bytes dentro de la palabra de memoria; es decir si son de orden big-endian o little endian. Estudiaremos varias alternativas de tratamiento. Desde la más simple de interpretar los bytes dentro de la palabra, pasando por interpretar los enteros largos que constituyen una palabra mayor; a métodos más generales que emplean uniones y campos; esta última no se recomienda ya que es dependiente de la implementación del compilador. Analizaremos la función estándar frexp. La función frexp extrae la mantisa y el exponente de un real: double frexp(double x, int * exponente) Dado un número real de doble precisión x, la función frexp calcula la mantisa m (como un real de doble precisión) y un entero n (exponente) tal que: x = m * (2n ) con: 0.5 =< m < 1 Como las funciones, en el lenguaje C, sólo retornan un valor, y si éste es el de la mantisa, debe pasarse un segundo argumento por referencia: la dirección de un entero; y la función devolverá el exponente escrito en el entero. Por esta razón el segundo argumento es un puntero a entero. El valor de la mantisa es el valor retornado por la función. Se tiene la siguiente representación externa para un número real: x = (-1)S 1.M2 2ee Donde S es el bit del signo, M2 la mantisa binaria, y ee es la representación externa del exponente, esto asumiendo representación de reales normalizados en formato IEEE 754. Dividiendo y multiplicando por dos, obtenemos: x = (-1)S 1.M2 2 -1 2 ee + 1 Entonces el número real que debe retornar la función, como mantisa mayor que un medio y menor que uno es: mantisa = (-1)S 1.M2 2 -1 y el exponente, que retorna frexp, debe ser: exponente = ee + 1. Prof. Leopoldo Silva Bijit. 18-08-2003 37 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Programación Avanzada en C La función debe extraer la representación interna del exponente, pasarla a representación externa y sumarle uno para formar el exponente, que retorna la función. Por otra parte debe convertir el exponente externo con valor menos uno a representación interna, y sobrescribirlo en la parte binaria dedicada al exponente. Se tiene que: exponente externo = exponente interno – polarización. El exponente externo se representa como un número con signo en complemento a dos, y la polarización es tal que el número más negativo (que tiene simétrico positivo) se represente como una secuencia de puros ceros. Para flotantes de simple precisión, que empleen 32 bits, se dedican 8 bits mayor positivo en complemento a dos es, en decimal, 127; que equivale binario. El número más negativo, -127, se representa en complemento a dos cumpliéndose que, para este número, la representación interna es: polarización para tipo float es 127, en decimal. al exponente, el a 01111111 en como: 10000001, 00000000. La Para reales de precisión doble, se emplean 64 bits, y 11 para el exponente; en este caso la polarización es 1023 en decimal, con representación binaria complemento a dos: 01111111111 (0x3FF en hexadecimal). Entonces para doble precisión, para un exponente externo igual a menos uno, debe escribirse en la parte que representa el exponente interno: -1 + 1023 = 1022 que equivale a 01111111110 (0x3FE). Para el exponente retornado por la función se tiene: ee + 1 = ei – 1023 + 1 = ei –1022. Acceso por caracteres (bytes). Para extraer el exponente, supongamos que el puntero a carácter pc apunta al byte más significativo del double; y que ps apunta al segundo. unsigned char * pc; unsigned char * ps; unsigned int ei; int exponente; Los últimos 7 bits del primer byte (*pc & 0x7F) son los primeros siete del exponente (ya que el primero se emplea para el signo del número). Los primeros 4 bits del segundo, son los últimos 4 del exponente interno. Para esto es preciso desplazar en forma lógica, en cuatro bits, esto se logra con: *ps>>4. Prof. Leopoldo Silva Bijit. 18-08-2003 38 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Programación Avanzada en C Para formar el exponente interno se requiere desplazar, en forma lógica, los primeros siete bits en cuatro posiciones hacia la izquierda. Entonces: ei = (*pc & 0x7F)<<4 | (*ps>>4) forma el exponente interno, como una secuencia binaria de 11 bits. Al depositarlo en un entero sin signo, los primeros bits quedan en cero (desde el doceavo hasta el largo del entero). Finalmente, se logra: exponente = ei –1022; Para sobrescribir el número 0x3FE, en las posiciones en que va el exponente interno, se requiere modificar los últimos siete bits del primer byte, para no alterar el signo del número. Esto se logra haciendo un and con la máscara binaria 10000000(0x80) y luego un or con la máscara binaria 00111111(0x3F) Es decir: *pc = (*pc & 0x80) | 0x3F; Para el segundo byte, sólo se deben sobrescribir los primeros cuatro. Esto se logra haciendo un and con la máscara binaria 00001111(0x0F) y luego un or con la máscara binaria 11100000(0xE0) Es decir: *ps = (*ps & 0x0F) | 0xE0; El resto de los bits con la mantisa del número no deben modificarse. Para apuntar a los primeros dos bytes, debe conocerse el orden de los bytes dentro de las palabras de la memoria. Esto es dependiente del procesador. En algunos sistemas el primer byte (el más significativo dentro del double) tiene la dirección menor, en otros es la más alta. Como casi todos los tipos de datos que maneja un procesador suelen ser múltiplos de bytes, para obtener la dirección de una variable de cierto tipo (en este caso de un double) en unidades de direcciones de bytes puede escribirse: unsigned char * pc = (unsigned char *)&number; unsigned char * ps; El moldeo (cast) convierte la dirección de la variable number en un puntero a carácter. Luego de esto, considerando que un double está formado por 8 bytes se tiene: pc += 7; ps=pc-1; para sistemas en que el byte más significativo tiene la dirección de memoria más alta. O bien: ps = pc +1; si el byte más significativo tiene la dirección menor; en este caso, no es preciso modificar pc. Prof. Leopoldo Silva Bijit. 18-08-2003 39 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Programación Avanzada en C Entonces el código completo de la función frexp puede escribirse: double frexp(double number, int *exponent) { unsigned char * pc = (unsigned char *)&number; unsigned char * ps; unsigned int ei; pc += 7; ps=pc-1; /* Big endian. O bien: ps=pc +1, para little endian*/ ei = ((*pc & 0x7F)<<4) | (*ps>>4); /*extrae exponente interno */ *exponent = ei - 1022; /*escribe en el entero que se pasa por referencia*/ *pc = (*pc & 0x80) | 0x3F; /*deja exponente igual a -1 */ *ps = (*ps & 0x0F) | 0xE0; return( number); } Sin embargo esta rutina tiene varias limitaciones. No trata número sub-normales y no detecta representaciones de infinito y NaN. Uso de dos enteros largos sin signo, para representar los bits de un double. Obviamente esto sólo puede aplicarse si los enteros largos son de 32 bits. Considerando ei como el exponente interno y ee como el exponente externo, se tienen: ee = ei -1023; ei = ee + 1023 Entonces, de acuerdo a la interpretación IEEE 754, se tiene que: Con ei = 0 y M2 != 0 se tienen números subnormales que se interpretan según: N = (-1)S*0.M2 *pow(2, -1022) Con ei = 0 y M2 == 0 se tienen la representación para el 0.0 según: N = (-1)S*0.0 Con 0< ei < 2047 se logran en forma externa: -1023 < ee < 1024, se tienen representaciones para números normales, según: N = (-1)S*1.M2 *pow(2, ee) Con ei = 2047 y M2 == 0 (ee = 1024) se tiene la representación para el ∝ según: N = (-1)S*INF Con ei = 2047 y M2 != 0 se tienen la representación para el ∝ según: N = NaN Prof. Leopoldo Silva Bijit. 18-08-2003 40 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Programación Avanzada en C El exponente interno se está considerando de 11 bits, sin signo. En la norma IEEE 754 debe considerarse números con signo. Para los dos últimos casos esto implica ei = -1. Entonces con las definiciones: unsigned long int *pm2=(unsigned long int *)&number; unsigned long int *pm1=pm2+1; Podemos apuntar con pm1 al entero largo más significativo, donde se almacena el signo, los 11 bits del exponente y 4 bits de la mantisa. Podemos conocer el signo del número mediante: int signo=( int)((*pm1)>>31); Dejando en signo un uno si el número es negativo; y cero si es positivo. La extracción del exponente interno, sin signo, se logra con: unsigned int ei= (unsigned int)(((*pm1)<<1)>>21); Primero se le quita el signo, y luego se desplaza a la derecha en 21 bits. Si se deseara manipular el exponente interno como número con signo, habría que definir: int ei=(int) ( ( ( long int)((*pm1)<<1)) >>21); Se corre a la derecha el largo con signo, y luego se convierte a entero. Las siguientes definiciones, nos permiten extraer la parte más significativa de la mantisa en m1 (20 bits), y la menos significativa en m2: unsigned long m1=(*pm1)&0x000FFFFFL; unsigned long m2=*pm2; Para tratar números subnormales es preciso normalizar la mantisa, corrigiendo el exponente. En el código se multiplica por dos el número y se resta uno al exponente, mientras primer dígito de la mantisa sea diferente de cero. Este primer dígito se detecta con la condición: ( (*pm1)&0x00080000L)==0 Setear el exponente externo en -1, para tener mantisa decimal que cumpla: 0.5 =< m < 1 se logra, como se explico antes, dejando el exponente interno en: 01111111110 (0x3FE). Lo cual se logra con: ((*pm1)&0x800FFFFFL)| 0x3FE00000L Para probar la rutina se pueden usar los siguientes valores: Para comprobar el cero: number = 0.0; Para verificar los subnormales: number = 0.125*pow(2,-1023); Debe resultar como respuesta: 0.5*pow(2,-1025); Para probar número grandes: number = 1.0*pow(2, 1023); Para probar el infinito: number = 1/0.0; Para probar un Not a Number: number = 0.0/0.0; Prof. Leopoldo Silva Bijit. 18-08-2003 41 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Programación Avanzada en C El código completo para la función: double frexp(double number, int *exponent) { unsigned long int *pm2=(unsigned long int *)&number; unsigned long int *pm1=pm2+1; unsigned long m1=(*pm1)&0x000FFFFFL; unsigned long m2=*pm2; unsigned int ei= (unsigned int)(((*pm1)<<1)>>21); if (ei==0) { if((m2|m1)==0) {*exponent=0;} /* 0.0 */ else {*exponent=-1022; while( ((*pm1)&0x00080000L)==0) {number*=2;(*exponent)--;} *pm1=((*pm1)&0x800FFFFFL) | 0x3FF00000L; number--; } else if (ei==2047) {if ((m2|m1)==0) printf("infinito \n"); /*ei==-1 con signo*/ else printf("NaN \n"); *exponent = 1025; *pm1=((*pm1)&0x800FFFFFL)| 0x3FE00000L;} else { *exponent = ei - 1022; /*escribe en el entero que se pasa por referencia*/ *pm1=((*pm1)&0x800FFFFFL)| 0x3FE00000L; } return( number); } Nótese que la misma rutina que no trata los casos subnormales y el cero, podría escribirse: double frexp(double number, int *exponent) { unsigned long int *pm1=((unsigned long int *)&number) +1; *exponent = ( (unsigned int)(((*pm1)<<1)>>21)) - 1022; *pm1=((*pm1)&0x800FFFFFL)| 0x3FE00000L; return( number); } Que equivale al comportamiento de la primera rutina que manipulaba los bytes del double. En los ejemplos de uso de union y campos, se desarrollará la misma rutina anterior. Prof. Leopoldo Silva Bijit. 18-08-2003 42 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Programación Avanzada en C Uso de union. Otra forma de accesar una zona de la memoria es a través de la estructura unión, que permite definir variables que comparten una zona común del almacenamiento. La unión asigna a la variable (de tipo union) un espacio de memoria suficiente para almacenar la variable de la union de mayor tamaño. En el ejemplo siguiente, la union denominada buffer, puede verse como un double o como una estructura denominada pbs. Las variables anteriores tienen la misma dirección de memoria, y se accesan de manera similar a una estructura. Si se escribe en una variable, se modifica la otra. La estructura pbs, define 64 bits, el mismo tamaño que el double. Y permite identificar los dos bytes más significativos del double, b0 y b1, en caso de que el byte más significativo esté ubicado en la dirección menor. Y b6 y b7 si el más significativo del double está asociado a la dirección mayor. union buf { struct bts {unsigned char b0; unsigned char b1; unsigned char b[4]; unsigned char b6; unsigned char b7; /*el más significativo con dirección mayor*/ } pbs; double d; } buffer; El valor +2.0 en doble precisión, equivale al valor 0x40000000 en formato IEEE 754. El signo es cero, la mantisa normalizada es cero. Y el exponente externo es +1. Para el exponente interno se cumple que: ei = ee + 1023 Empleando 11 bits en representación de números con signo polarizados, se tiene que 1023 decimal equivale a 0x3FF en hexadecimal. Entonces ei = 00000000001 + 01111111111 = 10000000000 = 0x400 en hexadecimal. Y resulta que el byte más significativo del double es 0x40, que equivale al binario: 01000000. Con la siguiente asignación puede escribirse en el double de la unión: buffer.d = 2.0; Y leer los bytes de la unión, accesando por su nombre los bytes de la estructura pbs. Prof. Leopoldo Silva Bijit. 18-08-2003 43 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Programación Avanzada en C if (buffer.pbs.b7==0x40) printf("el byte más significativo del double tiene la dirección mayor\n"); if (buffer.pbs.b0==0x40) printf("el byte más significativo del double tiene la dirección menor\n"); El siguiente diseño genera una función frexp portable a plataformas que empleen big o little endian para enumerar los bytes dentro de una palabra de memoria. La manipulación de los bytes es similar al diseño basado en leer bytes de una variable de gran tamaño, en base a punteros. double frexp(double number, int *exponent) { union buf { struct bts {unsigned char b0; unsigned char b1; unsigned char b[4]; unsigned char b6; unsigned char b7; /*el más significativo con dirección mayor*/ } pbs; double d; } buffer; unsigned int ei; buffer.d=2.0; if (buffer.pbs.b7==0x40) {buffer.d = number; ei=(unsigned int)(buffer.pbs.b7 & 0x7F)<<4|((unsigned int)(buffer.pbs.b6>>4)); *exponent=-1022+ei; buffer.pbs.b7 = (buffer.pbs.b7 & 0x80)|0x3F; buffer.pbs.b6 = (buffer.pbs.b6 & 0x0F)|0xE0; return( buffer.d); } if (buffer.pbs.b0==0x40) {buffer.d = number; ei=(unsigned int)(buffer.pbs.b0 & 0x7F)<<4|((unsigned int)(buffer.pbs.b1>>4)); *exponent=-1022+ei; buffer.pbs.b0 = (buffer.pbs.b0 & 0x80)|0x3F; buffer.pbs.b1 = (buffer.pbs.b6 & 0x0F)|0xE0; return( buffer.d); } *exponent = 0; /*no es little ni big endian */ return( number); } Prof. Leopoldo Silva Bijit. 18-08-2003 44 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Programación Avanzada en C Uso de campos (fields) El lenguaje C provee una estructura de campos de bits. Un campo de bits es un elemento de una estructura que es definida en términos de bits. Es dependiente de la implementación del lenguaje en un determinado procesador, pero asumiremos que está implementada con a lo menos 16 bits de largo, en total. Entonces en el ejemplo siguiente, dentro de la unión buffer, se tiene la estructura pbs, que a su vez está formada por la estructura campos1, el arreglo de 4 caracteres b, y la estructura campos2. Tanto la estructura pc1 como pc2 están formadas por campos de bits. Se han definido de largo 11 los campos exp1 y exp2, que tratan como secuencias de bits a las posibles ubicaciones del exponente de un double en formato IEEE 754. union buf { struct bts { struct campos1 { unsigned int unsigned int unsigned int } pc1; unsigned char b[4]; struct campos2 { unsigned int unsigned int unsigned int } pc2; } pbs; double d; } buffer; signo1 :1; exp1 :11; man1 :4; man2 :4; exp2 :11; signo2 :1; /*el byte más significativo con dirección mayor*/ Con la siguiente asignación puede escribirse en el double de la unión: buffer.d = 2.0; Y leer los grupos de bits de la unión, accesando por su nombre los campos de la estructura pbs. if (buffer.pbs.pc2.exp2==0x400) printf("el byte más significativo del double tiene la dirección mayor\n"); if (buffer.pbs.pc1.exp1==0x400) printf("el byte más significativo del double tiene la dirección menor\n"); El siguiente diseño implementa frexp usando estructuras de campos de bits (fields). Prof. Leopoldo Silva Bijit. 18-08-2003 45 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Programación Avanzada en C double pfrexp(double number,int *exponent) { union buf { struct bts { struct campos1 { unsigned int signo1 :1; unsigned int exp1 :11; unsigned int man1 :4; } pc1; unsigned char b[4]; struct campos2 { unsigned int man2 :4; unsigned int exp2 :11; unsigned int signo2 :1; /*el más significativo con dirección mayor*/ } pc2; } pbs; double d; } buffer; unsigned int ei; buffer.d=2.0; if (buffer.pbs.pc2.exp2==0x400) {buffer.d = number; ei=(unsigned int)(buffer.pbs.pc2.exp2); *exponent=-1022+ei; buffer.pbs.pc2.exp2 = 0x3FE; return( buffer.d); } if (buffer.pbs.pc1.exp1==0x400) {buffer.d = number; ei=(unsigned int)(buffer.pbs.pc1.exp1); *exponent=-1022+ei; buffer.pbs.pc1.exp1 = 0x3FE; return( buffer.d); } *exponent=0; return( number); } Prof. Leopoldo Silva Bijit. 18-08-2003 46