Buscar en Google

viernes, 11 de marzo de 2011

Operaciones aritmeticas

Operaciones aritméticas


Hasta ahora hemos hablado de las variables. Cuales son sus nombres char, int, long, float. También hemos visto cuanta memoria ocupa cada tipo de variable en la memoria RAM de los microcontroladores. De ahí fuimos hasta los sistemas de numeración. Comenzamos con una pequeña introducción al sistema de numeración que es natural para los seres humanos - el sistema decimal-. Luego les conté de la importancia de utilizar los sistemas binarios y hexadecimal.

Como continuidad del tema de variables y sistemas de numeración, debemos ahora entrar en lo que son las operaciones matemáticas. Acá vamos a ver como se comporta el microcontrolador cuando tiene que realizar tareas tales como sumas y restas. Veremos como es la representación para el C de los números negativos. También les mostraré algunos trucos del lenguaje C que nos permiten “ahorrar” un poco de líneas de código.

Operadores aritméticos y prioridades


Cuando programamos en C, tenemos las siguientes operaciones aritméticas básicas que podemos realizar: suma, resta, multiplicación y división.

Los operadores son los signos que deben utilizarse en la programación para indicarle al compilador que es lo que se quiere hacer. En nuestro caso, un cálculo aritmético sencillo. Entonces los operadores que se usan son:

                                  
suma
+
resta
-
multiplicación
*
división
/

Entonces, cuando queremos indicarle a nuestro programa que realice alguna operación aritmética sencilla, tenemos que utilizar los operadores que les mostré en la tablita anterior.

Por ejemplo, si tenemos un programa que queremos hacer que una variable llamada “C” sea igual a la suma de la variable “A” con la variable “B”, el código de programa que debemos colocar es:

C = A + B ;

Recordar que en todo código de programa escrito en C, SIEMPRE debe finalizar con un punto y coma.

Ahora, si tenemos que escribir un programa, donde una variable “C” queremos que sea igual al resultado de multiplicar la variable “A” por la variable “B” debemos colocar como línea de código la siguiente instrucción:
C = A * B;

De forma similar para las otras operaciones, resta y división:

C = A - B;   <- Resta
C = A / B; <- División

Ahora, ¿que sucede cuando tenemos en una misma instrucción todo mezclado? Por ejemplo, si tenemos la siguiente instrucción:

D = A + B * C;

¿Como funciona en el microcontrolador la instrucción que les acabo de pasar? Acá es donde aparecen las prioridades en los operadores. Las mismas indican que operaciones se deben realizar primero, y cuales después.

Las prioridades entre los operadores suma, resta, multiplicación y división es la siguiente: multiplicación o división tienen mayor prioridad que la suma o resta. O sea, primero se calcula la operación de multiplicar o dividir, y después se calculan con el resultado obtenido las otras operaciones (sumas o restas).

Entonces, supongamos que tenemos una línea de código con la siguiente operación matemática:

A = 10 + 2 * 7 - 9 / 3;

El microcontrolador va a resolver primero las operaciones multiplicación y división. Como en el ejemplo que les estoy pasando hay ambas cosas, va a resolver primero una y después otra ¿cual de las dos primero?, eso no lo podemos determinar, depende del compilador y como estén declaradas los parámetros de optimización. Lo que sí es seguro que no va a resolver ambas al mismo tiempo, lo hará en dos pasos diferentes. Pero, no hay problema en que pensemos que resuelve las operaciones recorriendo la fórmula desde la izquierda a la derecha.

En un primer paso, resolverá entonces la multiplicación, en nuestro caso 2 * 7 = 14. Entonces luego de ejecutar el primer paso, la instrucción antes dada, queda como:

A = 10 + 14 - 9/3;

Ahora, vuelve a reevaluar la operación matemática nuevamente. Tenemos pendiente la división que tiene mayor prioridad, así que se debe resolver esa operación. Entonces, el microcontrolador evaluará la operación 9/3 = 3. Quedando para el próximo paso:

A = 10 + 14 - 3;

Todavía el microcontrolador no llegó al resultado, por lo tanto, tiene que continuar evaluando la instrucción. Ahora, tenemos sólo sumas y restas que tienen la misma prioridad. Igual que antes, no podemos determinar cual será el verdadero orden  que utilizará el compilador para resolverlo, ya que depende de las optimizaciones y cual sea el compilador utilizado. De todas maneras, eso no afecta al resultado. Podemos asumir que se van resolviendo las operaciones desde la izquierda hacia la derecha. Entonces, el próximo paso será resolver la suma 10 + 14 = 24:

A = 24 - 3;

Finalmente, queda todavía un paso más, que es terminar de hacer la resta 24 - 3 = 21
Entonces, el último paso que ejecutará el microcontrolador, será asignar el valor resultante a la variable “A”

A = 21;

Notemos que para resolver ésta operación matemática, relativamente sencilla, hemos tenido que hacer varios pasos, mínimamente un paso por cada operación. En realidad, lo que hemos hecho, son iteraciones, ésto es, resolvimos una parte del problema y volvimos a evaluar la operación completa pero reemplazando el valor obtenido en el paso anterior.

La intención que tuve al mostrarle de ésta manera la forma de resolver una operación algebraica, es la complejidad que ésto implica. Si bien, un microcontrolador que corre a 5 millones de instrucciones por segundo, ésto no parece ningún tipo de problema. Pensemos si nuestro microcontrolador estará realizando una tarea, donde tiene que estar tomando datos con tasa alta de ingreso de datos por segundo. Por ejemplo, si nuestro microcontrolador tiene que estar capturando datos de algún periférico cualquiera. Y dicho periférico está enviando cien mil datos por segundo, y a cada dato hay que aplicarle una operación simple como esa que les mostré recién, donde se ocuparon 4 pasos solo en la resolución de ésa operación. Si nos están ingresando datos a una tasa de 100000 datos por segundo. Sólo ésta operación nos consume 4 pasos, o sea que nuestro microcontrolador tendrá que soportar ahí 400000 pasos por segundo. Todavía estamos lejos de los 5 millones de pasos por segundo. Parece que el microcontrolador podría funcionar, pero, no hay que perder de vista que nuestro programa no sólo va a ejecutar la operación aritmética que puse en el ejemplo, sino que muy probablemente tendrá decenas o cientos de otras instrucciones que tendrá que ejecutar antes de llegar a la operación aritmética. En la práctica, cuando utilizamos un microcontrolador de 8 bits, como puede ser un PIC, y que tiene colocado un cristal de 20 Mhz, que termina dando 5 MIPS o 5 millones de instrucciones por segundo (les recuerdo que los PIC cada cuatro ciclos del cristal avanza una instrucción), es muy difícil que puedan superar la barrera de los 15000 datos por segundo, incluso menos. Por lo tanto, cuando pensemos en éste tipo de microcontroladores, que están en el orden de los 5 MIPS, debemos saber donde está el límite. Para el caso de microcontroladores Texas, que tienen (los más chicos) una velocidad de procesamiento de 8 MIPS, podemos pensar proporcionalmente, y animarnos a llegar a los 24000 datos por segundo. Incluso tal vez, podemos darle un 50% más de margen ya que estos micros tienen procesamiento en 16 bits, que ayuda notablemente (ya lo voy a explicar un poco más adelante). Si tenemos un microcontrolador de la serie 8051, pensemos que se ejecuta una instrucción cada 12 ciclos de reloj, y podemos llegar a poner un cristal de hasta 30 Mhz, por lo tanto nos da un rendimiento máximo de 2,5 MIPS. Como el 8051 es un micro de 8bits, la barrera que debemos tener en mente que estaría en el orden de las 7000 muestras por segundo.
Estas barreras, son valores que a su vez son muy relativos, ya que dependen en gran medida de la cantidad de operaciones que se deben realizar con cada muestra o dato que se debe procesar. A mayor cantidad de operaciones, menor va a ser la velocidad máxima que podremos obtener y viceversa.

Volvamos al tema de las prioridades en las operaciones aritméticas. ¿Cómo puedo hacer para forzar al compilador para que me calcule primero una operación suma o resta y después una multiplicación o división? Para ello, es que en las operaciones aritméticas pueden utilizarse los paréntesis. Cuando el compilador encuentra un paréntesis dentro de una operación, procesa primero todo lo que queda encerrado dentro de éstos, y al finalizar el cálculo, continúa con lo que queda afuera del paréntesis. Si dentro del paréntesis hay otra operación aritmética que a su vez tiene varias operaciones, incluso otros paréntesis. El compilador aplicará nuevamente las prioridades. La resolución de las operaciones, cuando hay paréntesis, se ejecuta de manera que se comienza por los paréntesis más internos en la operación algebraica para ir saliendo hacia afuera.

Coloco en pasos consecutivos, una operación aritmética, y les voy mostrando como se va resolviendo:

Operación aritmética

A = 20 - 4 * 7 + (45 - 3) * 2 - 9 * (4 + (3 + 3) / 2) ;

Primer paso, resuelve paréntesis más interno

A = 20 - 4 * 7 + (45 - 3) * 2 - 9 *(4 + 6 / 2);

Segundo paso, resuelve el primero de los paréntesis de izquierda a derecha

A= 20 - 4 * 7 + 42 * 2 - 9 * ( 4 + 6  / 2);

Tercer paso, resuelve el segundo paréntesis, que a su vez, tiene internamente otras operaciones, y dentro de esas operaciones, la división tiene mayor prioridad que la suma.

A = 20 - 4 * 7 + 42 * 2 - 9 * ( 4 + 3 );

Cuarto paso, continúa resolviendo la operación dentro del único paréntesis que quedaba

A = 20 - 4 * 7 + 42 * 2 - 9 * 7;

Quinto paso, resuelve la primera multiplicación de izquierda a derecha

A = 20 - 28 + 42 * 2 - 9 * 7;

Sexto paso, resuelve la multiplicación

A = 20 - 28 + 84 - 9 * 7;

Septimo paso, resuelve la multiplicación

A = 20 - 28 + 84 - 63 ;

Octavo paso, quedan solo sumas y restas, comienza por la primer resta de izquierda a derecha

A = -8 + 84 - 63;

Noveno paso, continúa con la suma

A = 76 - 63;

Décimo paso, el resultado ¡por fín!

A = 13;


Veamos ahora, como funcionan las operaciones matemáticas, realmente dentro del microcontrolador. Que sucede con los tipos de variables cuando se ejecutan los diferentes tipos de operaciones.  Así como les conté paso a paso como se resuelve una operación aritmética, también les voy a contar ahora a nivel de bit como el microcontrolador procesa los diferentes tipos de operaciones. Si comprendemos ese funcionamiento, nos va a ser más fácil detectar potenciales problemas. También vamos a tener en la mano una herramienta poderosa que puede indicarnos como plantear nuestros programas para que sean eficientes, y resuelvan la función que tenemos como objetivo en cada programa que tengamos que realizar.

Comencemos con la operación más básica:

Suma y resta


Esta es la operación matemática más básica. Vamos a ver ahora, como se aplica la suma en el sistema binario, para entender como lo interpreta el microcontrolador para los diferentes casos. Voy a poner siempre, además del número en binario, el mismo valor en hexadecimal para que vayamos acostumbrándonos a usarlo, y aprenderlo por insistencia.

Supongamos que tenemos que sumar dos números, de la siguiente manera:

A = 18 + 15;

Los valores en binario son 18 = 00010010,  hexadecimal 0x12 y 15 = 00001111 en hexadecimal 0x0F.

La suma en binario se realiza de la misma manera que una suma en sistema decimal. Esto es, se comienza sumando primero los valores menos significativos y se va avanzando hacia los más significativos.

Colocamos entonces los valores en columnas


0
0
0
1
0
0
1
0
0x12
+
0
0
0
0
1
1
1
1
0x15
=











Sumamos la primera columna de la derecha, 0 + 1  = 1


0
0
0
1
0
0
1
0
0x12
+
0
0
0
0
1
1
1
1
0x0F
=







1


Seguimos por la segunda columna de la derecha, 1 + 1  = 10, o sea pongo un 0 y me llevo 1







1




0
0
0
1
0
0
1
0
0x12
+
0
0
0
0
1
1
1
1
0x0F
=






0
1


La tercera columna, es de nuevo 1 + 0 + 1 = 10, me llevo uno y pongo el 0






1





0
0
0
1
0
0
1
0
0x12
+
0
0
0
0
1
1
1
1
0x0F
=





0
0
1


Cuarta columna, de nuevo 1 + 0 + 1 = 10, me llevo uno y pongo el 0





1






0
0
0
1
0
0
1
0
0x12
+
0
0
0
0
1
1
1
1
0x0F
=




0
0
0
1





Quinta columna, es 1 + 1 + 0 = 10, me llevo uno y pongo el 0




1







0
0
0
1
0
0
1
0
0x12
+
0
0
0
0
1
1
1
1
0x0F
=



0
0
0
0
1


Sexta columna, es 1 + 0  + 0 = 1












0
0
0
1
0
0
1
0
0x12
+
0
0
0
0
1
1
1
1
0x0F
=


1
0
0
0
0
1


Séptima y octava columna, son 0+0 = 0













0
0
0
1
0
0
1
0
0x12
+
0
0
0
0
1
1
1
1
0x0F
=
0
0
1
0
0
0
0
1
0x21

Quise describir con éstos pasos como funciona la operación suma dentro de un microcontrolador, para que entendamos como es el funcionamiento bit a bit. En el  100% de los microcontroladores, ésta operación se hace en forma directa, y ocupa sólo un ciclo de instrucción cuando la suma es entre 2 variables donde ambas son de 8 bits cada una. En los microcontroladores de 16 bits, se tiene también que la suma de variables de 16 bits se realiza en un solo paso.

Puse también al costado de cada suma, sus valores en hexadecimal, para que veamos como es la suma cuando se trabaja en sistemas hexadecimales, y a partir de ahí, seguimos sólo con el sistema hexadecimal.

Veamos como es sumar 0x12 + 0x0F en hexadecimal.


1
2
+
0
F
=



La primera suma, es 0x2 + 0xF. 0xF es el valor 15 en decimal, así que 2 + 15 = 17. 17 en decimal es 0x11 en hexadecimal. Así que 0x2 + 0xF = 0x11. O sea, pongo 1 en el primer dígito a la derecha y me llevo 1.


1


1
2
+
0
F
=

1

Ahora, el segundo dígito es 1+1 = 2


1


1
2
+
0
F
=
2
1

Evidentemente, el resultado 0x12 + 0x0F es igual a 0x21 que debe ser el mismo resultado obtenido de realizar la suma en el sistema binario. Y para corroborar, 0x12 es 18 en decimal, 0x0F es 15 en decimal, 18 + 15 = 33 que también es 0x21.

Estas cosas no son visibles para el programador, y se preguntarán porqué redundo en esto. Para el programador, todo se resuelve en sólo una instrucción. Sin embargo, si entendemos ésta característica, entenderemos que implícitamente en una misma suma se puede resolver también una resta con un truco muy sencillo que veremos a continuación.

Números negativos


Veamos que sucede cuando sumamos dos variables de 8 bits, y que tienen los valores 0x33 y 0xFF

Si nuestro hacemos un programita para esa suma, sería:

unsigned char A;
unsigned char B;
unsigned char C;

A=0x33;
B=0xFF;

C=A+B;

Hagamos entonces la suma, siguiendo el proceso que les describí, directamente usando el sistema hexadecimal.

Completo la tablita




3
3
+
F
F
=



Sumo las unidades, 0x3 + 0xF = 0x12, tengo que colocar el 2 y me llevo 1


1


3
3
+
F
F
=

2

Sumo el nibble mas significativo 0x1 + 0x3 + 0xF = 0x13.  Tengo que colocar el 3 y me llevo 1




3
3
+
F
F
=
3
2

¿Donde puse el 1 que me llevaba? En ningún lado, porque las variables que estaba usando, estaban declaradas como unsigned char, o sea son de 8 bits. El resultado no entra en la variable, y se trunca a sólo los dos dígitos menos significativos.

Esta suma me produjo un desbordamiento. Por lo tanto, si yo hubiera querido almacenar toda la información del resultado, esto es: 0x33 + 0xFF = 0x132, debería haber declarado la variable C como un tipo de datos de 16 bits, como puede ser el tipo int. Acá vemos la importancia de tener en claro como se declaran los tipos de datos. Vemos que una operación suma nos puede dar un resultado no esperado, puesto que al sumar dos valores, el resultado puede ser más grande que el máximo valor posible de almacenar en el tipo de variable que hayamos declarado.

De todas maneras, el microcontrolador hará la suma, no pondrá ningún tipo de  objeción y presentará el resultado como 0x32.

Puse con toda intención ese valor 0xFF, para que notemos que el resultado que obtenemos cuando en el programa hemos declarado la variable de salida también como de 8bits.

Lo que quiero que observen que hacer 0x33 + 0xFF = 0x132, y al almacenarlo en una variable de 8 bits queda como 0x32.

¿Que diferencia para nuestro programa tiene hacer 0x33 + 0xFF con 0x33 - 1? Ninguna. Lo que nos está mostrando, que para este caso 0xFF es igual a -1.

Si repito lo mismo con el valor 0xFE, veremos que hacer 0x33 + 0xFE = 0x131, o sea, para nuestro programa el resultado será 0x33 + 0xFE = 0x31. Lo que es lo mismo que hacer 0x33 - 2.

Si repito con todos los valores posibles, vamos a encontrar que todas las sumas pueden ser interpretadas siempre como una suma con un valor negativo.

La regla es que los números se pueden representar como negativos, comenzando por el -1 que equivale al 0xFF, y se van aumentando a medida que bajamos el valor desde 0xFF hasta 0x80. Así, tenemos la posibilidad de asignar valores desde -1 hasta -128 donde -1 equivale a 0xFF y 0x80 equivale a -128.

¿Por qué no sigo descendiendo hasta cero para lograr números negativos hasta -255? Por convención del estandard de C. Donde se define que los números negativos en una variable son cuando el bit más significativo está en 1. Notemos que todos los valores desde el 0x80 hasta el 0xFF siempre poseen el bit 7 o bit más significativo en 1. Les muestro los valores en binario y se van a dar cuenta:

Valor
7
6
5
4
3
2
1
0

0xFF (255)
1
1
1
1
1
1
1
1
Equivale a -1
0x80 (128)
1
0
0
0
0
0
0
0
Equivale a -128 es el número negativo más grande que puedo representar con 8 bits
0x7F (127)
0
1
1
1
1
1
1
1
Equivale a +127 siempre. El bit 7 está en cero
0x00 (0)
0
0
0
0
0
0
0
0
Equivale a 0.

Con ésta notación, algunos ya se han dado cuenta que no es necesaria la operación resta ¿ya se dieron cuenta? La resta no es más que la suma de un número positivo con otro negativo.

Algunos modelos de microcontroladores, no tienen la función resta. El compilador es el encargado de hacer la conversión. Cuando colocamos una resta tal como 10 - 4 = 6.  El compilador lo que hace es cambiar la resta por una suma, pero el dato que carga es el valor cambiado de signo. Por lo que para el caso de los microcontroladores que no traen la resta, el compilador hace 10 + (-4) = 6.

Si extrapolamos el caso de 8 bits al caso de 16 y 32 bits. Vamos a ver que es exactamente lo mismo. De ahí, es que sale porqué los tipos de datos de 16 bits permiten almacenar valores con signo desde -32768 hasta +32767. Y los de 32 bits desde -2147483648 hasta +2147483648.




Sumas y/o restas de números más grandes


Si nuestro microcontrolador es de 16 bits, y queremos sumar dos números que son también de 16 bits, ya dije que no hay problema. El microcontrolador tiene incorporado dentro de sus circuitos la solución. Existe en ellos la instrucción, y la operación matemática se hace en sólo un ciclo de procesador. Por lo que si nuestro microcontrolador está funcionando a  5 millones de instrucciones por segundo, en un caso ideal (no práctico ni real) es posible realizar 5 millones de sumas por segundo.

Pero, en microcontroladores de 8 bits no es posible sumar dos números de 16 bits en forma directa ¿qué sucede cuando nuestro microcontrolador es de 8 bits y tenemos que hacer una suma de 16 bits?

Lo que tiene que hacer el compilador, es exactamente la suma, tal cual la hacemos a mano, en varios pasos hasta obtener el resultado. Por ejemplo, si tenemos que sumar dos números cualquiera de 16 bits, para almacenar el resultado en 16 bits:

int A;
int B;
int C;

A = 0x0123;
B=0x0380;
C= A+B;

El microcontrolador, puede en forma nativa sumar dos variables de 8 bits, así que agrupamos los valores en 2 grupos de 8 bits cada uno:


01
23
+
03
80
=



Se hace primero la suma del byte menos significativo 0x23 + 0x80 = 0x103. Como 0x103 no entra en un byte, pero el resultado va a estar en 16 bits, entonces, me llevo el 1 y lo sumo a la columna de bytes mas significativos


1


01
23
+
03
80
=

03

Finalmente, sumo los bytes más significativos, 0x01 + 0x01 + 0x03 = 0x05


1


01
23
+
03
80
=
05
03

Y el resultado de sumar 0x123 + 0x380 = 0x0503.

¿Cuantos pasos se utilizaron? Tres. Se usó un primer paso para sumar los bytes menos significativos. Un segundo paso para corroborar si me llevé un uno o no. Y el tercer paso la suma de los bytes más significativos.

Esto quiere decir, que para sumar dos variables de 16 bits, en un microcontrolador de 8 bits se necesitan 3 instrucciones o 3 ciclos. Por lo tanto, ya comenzamos a notar las diferencias entre un procesador de 8 bits y otro de 16 bits. Si nuestro microcontrolador de 8 bits trabaja a 5 MIPS, entonces, la máxima cantidad de sumas posibles de dos variables de 16 bits será de 1,66 millones de sumas por segundo.

Si planteamos el mismo caso para una suma de 32 bits, vamos a obtener que con un microcontrolador de 32 bits, las sumas en 32 bits se efectúan en un sólo ciclo. Si en cambio, nuestro micro es de 16 bits, se necesitan 3 ciclos. En cambio, si nuestro microcontrolador es de 8 bits, se necesitan 9 pasos.

Resumiendo, vemos que para el doble de capacidad de procesamiento en el ancho de bits, para microcontroladores que corren a la misma velocidad, se divide por 3 el tiempo que le toma a uno respecto al otro.



Suma 32 bits + 32 bits
Suma 16 bits + 16 bits
Suma 8 bits + 8 bits
Micro 8 bits
9
3
1
Micro 16 bits
3
1
1
Micro 32 bits
1
1
1
Comparativas de cantidad de ciclos necesarios para una operación suma según el tipo de microcontrolador, y el tipo de suma

Además, tengamos en cuenta que a medida que son más nuevos los microcontroladores, donde el ancho del bus, o cantidad de bits que pueden procesar son mayores. También la velocidad del micro es mucho mayor. Por ejemplo, los PIC de 8 bits, tienen una velocidad máxima de 5 MIPS. Los Texas mas chicos de 16 bits tienen  8 MIPS. Un ARM7 de 32 Bits ronda los 50 MIPS. Esto quiere decir, que entre un micro de 8 bits a 5 MIPS contra un ARM de 32 bits a 50 MIPS, sólo en velocidades hay una mejoría de 10 veces, pero para las sumas en 32 bits, ésto se multiplica por 9. O sea, para sumar dos valores en 32 bits, un ARM es 90 veces más rápido que un micro de 8 bits a 5 MIPS.


Sumas y/o restas entre diferentes tipos de variables. Promotion y demotion.


Ahora, metamos en una misma bolsa todo lo visto hasta aquí, ¿qué sucede si sumamos variables de diferentes tipos, por ejemplo una variable char con una variable tipo int, o cualquier otra combinación que se nos ocurra?

Para responder a ésta pregunta, tenemos que observar los siguientes puntos:

-       El tamaño en bits de la variable donde se almacena el resultado.
-       Los tamaños en bits de las variables que intervienen en la fórmula.

Ya vimos algo, sobre lo que pasa si el tamaño de la variable donde se almacena el resultado es menor al tamaño requerido.

Supongamos que se tienen que sumar dos variables de 16 bits, y el resultado se lo intenta almacenar en una variable de 8 bits, tendríamos un programa como el siguiente:

unsigned int A;
unsigned int B;
unsigned char C;

C = A + B;
Entonces, dependiendo de que valores tengan cargadas las variables A y B será la forma que se represente el resultado. Supongamos que las variables A y B que son de 16 bits, sumadas dan un valor resultado menor que 256. Entonces, en ése caso en la variable C va a estar almacenada la suma y el valor va a ser correcto, puesto que una variable tipo char, de 8 bits, permite almacenar como valor máximo el número 255.

Ahora, si la suma de las variables A y B, da un resultado que es mayor que 256, lo que va a suceder, es que cuando el microcontrolador haga la suma, el resultado no se va a poder almacenar dentro de la variable C en forma completa. Entonces, el resultado va a ser el byte menos significativo del resultado de la suma.

Por ejemplo, si A tiene cargado el valor 0x123 y B tiene cargado el valor 0x3123. La suma será 0x123 + 0x3123 = 0x3246. Pero como el resultado se debe almacenar en la variable C que es  sólo de 8 bits, entonces la variable C tendrá como resultado el valor 0x46. En éste caso se ha perdido la información del resultado de la suma. Hay algunos casos donde justamente se busca ésto. Sin embargo, en la mayoría de las veces, debemos tener cuidado de que la variable resultado tenga la capacidad de poder almacenarlo.


Veamos ahora que sucede cuando las variables que participan en la suma son de diferentes tamaños. Por ejemplo, tenemos un programa que está escrito de la siguiente forma:

unsigned char A;
unsigned int B;
unsigned int C;

.....

C = A + B;

Ahora es donde cobra importancia saber si el tipo de variables involucradas son con o sin signo. Para el caso del programa que les muestro, todas las variables fueron declaradas como tipo unsigned, es decir, todas las variables van a poseer sólo valores positivos.

El compilador cuando va a implementar las tareas para resolver la instrucción algebraica C=A+B, lo que hace es llevar o convertir temporalmente todas sus variables a la variable de mayor tamaño.

Entonces, el compilador interpretará la fórmula de la siguiente manera:

                                   (unsigned int)C=(unsigned int)A + (unsigned int)B

En éste caso no hay mayores complicaciones, el compilador simplemente acomodará el valor de la variable A en 2 bytes para formar una variable temporal de 16 bits, y luego realizar la suma como si fuera una suma normal entre 2 variables de 16 bits.

Ahora bien, si la variable A que es la de 8 bits tiene cargado el valor 0xFF. Ya vimos anteriormente que 0xFF para un dato de 8 bits, puede entenderse como un valor negativo o -1. Sin embargo, como esta variable A está declarada del tipo unsigned char. Entonces, el compilador reconoce esa condición, y asignará el valor +255 o 0x00FF a la variable temporal de 16 bits que reemplaza a la variable A para realizar el cálculo.

Si reescribimos el programa nuevamente, pero cambiamos la variable A de unsigned char por signed char. Estamos indicando explícitamente que queremos que la variable A sea considerada de 8 bits, pero que tenga en cuenta el signo.

signed char A;
unsigned int B;
unsigned int C;

......

C=A+B;


Lo que hace el compilador, es verificar el valor de la variable A antes de convertirlo a 16 bits. Si por ejemplo la variable A viene con el valor previamente cargado en 0xFF que significa -1. Entonces, el compilador no le va a asignar a la variable temporal de 16 bits un valor 0x00FF, sino que tomará el valor 0xFFFF. Ese valor significa -1 para los datos de 16 bits. Como regla, el microcontrolador, cuando se trata de variables con signo, y donde tiene que convertir una de las variables a una variable de mayor tamaño para realizar una cuenta, es que verifica, si la variable tiene cargado un valor positivo, entonces coloca los bits más significativos en 0. En cambio, si la variable posee cargado un valor negativo, entonces, colocará los bits más significativos en 1.

Estas operaciones que les acabo de indicar, que las realiza el compilador automáticamente, se conocen como promotion, o promoción. Que es cuando la conversión se debe hacer desde una variable con una menor cantidad de bits hacia otra con una cantidad de bits mayor. Cuando se hacen promotions no hay peligro de pérdida de bits, ya que la nueva variable puede contener el nuevo valor, pero debe tenerse muy cuidado con el tipo de variables involucradas, ya que el compilador las interpreta diferente si son con signo o sin signo.

La acción de tener que almacenar el resultado dentro de una variable de menor cantidad de bits, como vimos en el primer ejemplo, es lo que se conoce como demotion o degradación. En éste segundo podemos tener problemas con pérdida de información.


Multiplicación y división


Merece que demos un vistazo a la multiplicación y división para entender las consecuencias que ésto puede traer en nuestros equipos con microcontroladores.

Hay modelos de microcontroladores, por ejemplo el 8051, los PIC de la serie 18, y algunos modelos de los MSP430, que tienen incorporados un multiplicador por hardware.

Para los microcontroladores que ya traen incorporado un multiplicador por hardware no hay mayores complejidades. El microcontrolador ejecutará en un sólo ciclo del reloj la operación de multiplicación.

Los  puntos importantes que hay que notar cuando se realiza una multiplicación son los mismos casos que en las sumas y restas. Hay que tener mucho cuidado en el tamaño de la variable donde se almacena el resultado. Y tener en cuenta el tipo de variables que intervienen en la operación. No es lo mismo multiplicar dos variables que siempre son positivas, a multiplicar dos variables que poseen signos. El compilador hace las correspondientes promociones y degradaciones de las variables para acomodarlas y poder realizar la tarea.

Si el microcontrolador a utilizar posee multiplicador por hardware no hay más nada que decir. Se cumplen las mismas o similares observaciones que las que tienen la suma y resta.

Veamos ahora que sucede con la multiplicación, cuando utilizamos un microcontrolador que no posee multiplicación por hardware. Como resuelve el microcontrolador las operaciones de multiplicación.
Para poder entender como funciona la multiplicación en un microcontrolador, debemos recordar como se multiplica en el sistema decimal. Supongamos que tenemos que multiplicar dos números de 2 cifras cada uno: 12 x 34

La operación manual para realizar esta operación implica colocar ambos números encolumnados:


1
2
X
3
4




Luego se hace la multiplicación de las unidades del multiplicador por el multiplicando. O sea 4x12

1
2
X
3
4

4
8

Después, se hace la multiplicación de la decena del multiplicador por el multiplicando. O sea 3x12. Y se acomoda desplazado.


1
2
X
3
4

4
8
3
6


El resultado, es la suma de 48 + 380


1
2
X
3
4

4
8
3
6

4
0
8

Bien, lo que les conté fue solo un repaso de como multiplicamos normalmente como seres humanos, cuando tenemos que obtener el cálculo en forma manual.

En definitiva, el algoritmo que estamos aplicando en forma manual es el siguiente:

            1 - Multiplicar el dígito menos significativo del multiplicador por todo el multiplicando
            2 - Acumular el resultado de la multiplicación anterior
            3 - Multiplicar el digito siguiente del multiplicador por todo el multiplicando. El resultado debe ser desplazado hacia la izquierda en un dígito y acumularlo con el resultado obtenido en el paso 2.

La multiplicación de dos números en el microcontrolador, se hace de la misma manera, sólo que con números binarios. La ventaja que se tiene es que la multiplicación en binario sólo tiene dos valores posibles, 0 y 1. O sea, se usa la tabla de multiplicar del 1.

Bien, para multiplicar dos dígitos en forma binaria. Se utiliza el mismo algoritmo que les mencioné hace unas pocas líneas.

Veamos, si tenemos que multiplicar dos valores del tipo char, o sea dos valores de 8 bits cada uno, entonces, aplicando el método o algoritmo que les acabo de dar, notamos inmediatamente que tenemos que ocupar como mínimo 16 pasos para multiplicar 2 números de 8 bits.

El resultado de una multiplicación de dos números de 8 bits, pueden ocupar como resultado un valor en 16 bits.

De la misma manera, si tenemos que multiplicar dos números de 16 bits, necesitaremos como mínimo 32 ciclos de instrucciones y el resultado estará expresado en un valor de 32 bits.

Esto que les cuento, es para que entendamos que sucede dentro de un microcontrolador que no posee multiplicador por hardware. Supongamos que tenemos que escribir un programa como el siguiente:

char A;
char B;
int C;

A=cualquier valor;
B=cualquier otro valor;

C=A*B;

Tengamos en cuenta que esa operación está como mínimo necesitando 16 ciclos de instrucción. Si nuestro microcontrolador está corriendo a 5 millones de instrucciones por segundo, como máximo podremos hacer hasta 312500 operaciones por segundo.

En cambio, si nuestro microcontrolador posee multiplicador por hardware, dicha operación se hace en tan sólo un ciclo de procesador.

La operación multiplicación es muy utilizada cuando se hace procesamiento digital de señales, y filtros digitales. Donde se toman muestras de algún proceso mediante un procesador y se van ingresando dichas muestras a un algoritmo que aplica operaciones matemáticas. Si van a tener que implementar un proyecto donde estarán ingresando datos a una alta tasa o velocidad, y necesitan que esas muestras tengan algún tipo de procesamiento. Donde probablemente tengan que efectuar multiplicaciones. Piensen en utilizar algún tipo de microcontrolador con multiplicador por hardware, y preferentemente un DSP. Los DSP de la serie TMS320xxx trabajan en 16 y 32 bits y con velocidades de hasta 300 MIPS, y con multiplicador por hardware. Estos últimos, son unas verdaderas bestias que se comen los datos, son especiales cuando tenemos esos casos. La programación de estos chips, también se hace en C. Y tienen costos similares o no demasiado más caro que los microcontroladores mas completos. 

Las series de PIC18Fxx, tienen también multiplicador por hardware, pero su velocidad de procesamiento puede llegar hasta 10MIPS

Las series dsPIC son DSP de la firma microchips. Trabajan en 16 bits y llegan hasta los 30MIPS

Las series MSP430 de Texas, tienen microcontroladores con multiplicador que llegan hasta los 25MIPS

No hay comentarios:

Publicar un comentario