Rutina matemáticas. Trigonométricas. Para desarrollar el algoritmo

Anuncio
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
Descargar