Desplazamientos
En el lenguaje C, nos está quedando una operación que se puede realizar con las variables y que es muy interesante e importante conocer. La operación que está faltando es el desplazamiento. Esta operación, junto con las operaciones aritméticas y lógicas; componen la familia básica de herramientas que posee el C; y que además también en la mayoría de los microcontroladores tienen implementadas por hardware. Con el uso de estas 3 operaciones básicas, me atrevo a decir que se resuelven el 90% de los casos de programas.
La operación que nos toca en éste apartado es el desplazamiento. El desplazamiento consiste en cambiar de posición los bits de una variable de algún tipo. Por ejemplo, un desplazamiento hacia la izquierda significa hacer que el bit 7 cargue lo que tiene el bit 6, luego el bit 6 carga el valor que tiene el bit 5, y así sucesivamente hasta llegar al bit 1. El bit 0, por definición del C tomará el valor 0.
El signo que se utiliza para realizar desplazamientos es el << cuando se indica que se debe realizar un desplazamiento hacia la izquierda. Y el >> cuando se quiere desplazar hacia la derecha. La variable que queda a la izquierda del doble signo “<<” o “>>” es la variable que se va a desplazar. Mientras que el valor o variable que quedan del lado derecho del signo, debe indicar la cantidad de desplazamientos que se deben realizar. El valor que está colocado a la derecha, nunca debe ser mayor a la cantidad de bits que tiene la variable o el valor que se está desplazando, puesto que si se intenta desplazar más veces que la cantidad de bit que tiene el valor original, el resultado será obligatoriamente 0, ya que se van completando los bits agregados siempre con el valor 0.
Podemos ver en forma de gráfico como se produce el desplazamiento:
Supongamos que hacemos el siguiente programita:
char A;
char B;
A = 0x55;
B = A << 1;
Este programita, indica que la variable B tiene que tener como resultado el valor de la variable A, desplazado una vez hacia la izquierda.
Numero de bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
A=0x55 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
A << 1 = 0xAA | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | Se agrega un cero |
Podemos observar que al producir un desplazamiento, se agrega un cero en el bit 0 del resultado del desplazamiento. Y el bit 7 de la variable A original se descarta.
Lo interesante de ésta operación, es que cuando es un único desplazamiento. Se resuelve en un sólo paso dentro del microcontrolador. En algunos modelos de microcontroladores, y muchos DSP, tienen la posibilidad de realizar más de un desplazamiento al valor, y aún así tomar solo un ciclo del procesador.
Si lo que deseamos es realizar una mayor cantidad de desplazamientos, podemos por ejemplo escribir la línea como A << 4. Donde lo que significa que se rotará 4 veces hacia la izquierda el valor de la variable A
Numero de bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
A=0x55 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
A << 4 = 0x50 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | Se agregan 4 ceros |
Este último caso, si lo que se está utilizando es un microcontrolador PIC o un Texas MSP430xxx. Necesariamente ocupará como mínimo 4 ciclos de trabajo. Ya que ninguno de los dos posee un desplazamiento mayor a una vez, implementada por hardware.
De la misma manera que se realizan los desplazamientos hacia la izquierda, se pueden realizar desplazamientos hacia la derecha. Por ejemplo:
A >> 1
Significa que se tome el valor de la variable A y se le aplique un desplazamiento de sus valores hacia los bits de la derecha:
Numero de bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
A=0x55 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
A >> 1 = 0x2A | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
El funcionamiento consiste en hacer que el bit 0 del resultado cargue el valor del bit 1, luego el bit 1 toma el valor del bit 2, y así sucesivamente hasta llegar al bit 6. El bit 7 siempre se completa con 0.
De la misma manera que los desplazamientos a la izquierda, es posible realizar mayor cantidad de desplazamientos a la derecha. Por ejemplo:
A >> 3
Numero de bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
A=0x55 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
A >> 1 = 0x0A | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
Ahora bien. ¿Para qué querría alguien usar estas operaciones?
Respondiendo a esa comprensible pregunta, volvamos a las bases, cuando hablamos de números binarios y números decimales.
Veamos que sucede en nuestro sistema decimal cuando multiplicamos un número por 10, o por 100, o por 1000.
Supongamos que tenemos un número 123 en decimal y lo multiplicamos por 10. En forma mental ¿qué hacemos?¿como realizamos una multiplicación de un número por 10?
Simplemente agregamos un cero a la derecha y el número se multiplicó por 10:
123 x 10 = 1230
Cuando multiplicamos un número por 100, le agregamos mentalmente dos ceros a la derecha. Por ejemplo:
123 x 100 = 12300
Ahora pasemos al mundo binario, el mundo de nuestros microcontroladores. No voy a multiplicar por 10 en decimal, sino por 2, por 4, y por 8. Notemos que en binario el 2 es igual a 10. El 4 en binario se representa por 100. El 8 en binario se representa por 1000.
Supongamos que tenemos el valor 6 y lo multiplicamos por 2.
6 x 2 = 12
Ahora veamos los mismos valores en binario: 6 es 110. 2 es 10. Y el 12 mágicamente es 1100
110 (6) x 10 (2) = 1100 (12)
O sea, al multiplicar un valor cualquiera por 2, que en binario es 10. Si representamos el valor del número que queremos multiplicar por 2 en binario, la multiplicación consiste en agregar un cero a la derecha del número, y desplazar todos los otros dígitos una posición hacia la izquierda.
Si repetimos el proceso para la multiplicación x 4, vamos a ver que se cumple la misma regla, pero ahora agregando dos ceros a la derecha, y desplazando dos veces el valor hacia la izquierda. Notemos que 2x2 = 4. Por lo tanto, es lo mismo multiplicar un valor por 4, que multiplicarlo dos veces consecutivas por 2. O sea, agregar dos veces consecutivas un cero a la derecha.
6 x 4 = 24
110 (6) x 100 (4) = 11000 (24)
Lo mismo sucede para multiplicar por 8, por 16, por 32, y por cualquier potencia de 2. O sea, si tenemos que realizar una multiplicación de una potencia de 2 en un programa hecho en C. Se puede resolver de dos maneras diferentes.
Por ejemplo, vamos a resolver la multiplicación de dos valores por 8. Una de la soluciones es usar la operación aritmética multiplicación y la otra usando las rotaciones.
char A;
char B;
char C;
B = A * 8;
C = A << 3;
¿Cuando conviene una forma y cuando la otra?
Recordemos que una multiplicación, en un microcontrolador que no posee multiplicador por hardware, requería de muchos pasos de procesamiento. Suponiendo que el multiplicando y el multiplicador son de 8 bits, vimos que como mínimo se requerían de 16 pasos de procesamiento. Si utilizamos el desplazamiento, para la misma multiplicación de un número por 8 sólo se usan 3 pasos. O sea, si nuestro microcontrolador trabaja a 5 MIPS, cuando se utiliza la multiplicación normal, podremos como máximo lograr 312500 operaciones por segundo. En el otro caso, podremos llegar a las 1666666 operaciones por segundo.
No se puede utilizar el desplazamiento cuando el valor es negativo ya que la información con el signo no se respeta. Para ese caso, debe hacerse un truco que consiste en preguntar previamente si el valor era positivo o negativo. Convertir el valor a positivo, desplazarlo y luego convertirlo nuevamente en negativo.
De la misma manera que desplazar hacia la izquierda implica una multiplicación por una potencia de 2. El desplazamiento hacia la derecha implica la división en una potencia de 2. De nuevo la gran ventaja de usar desplazamientos en vez de una división, cuando el divisor es una potencia de 2 es que la cantidad de ciclos de procesamiento que se utilizan son mucho menores. Así, con esta herramienta se puede lograr que un microcontrolador de los más humildes, realice tareas que son impensadas si lo intentamos programar en la forma más natural que se nos ocurriría normalmente que es usando multiplicaciones y divisiones.
Para poder usar estas herramientas como ayudas matemáticas, tenemos que acostumbrarnos al uso de las potencias de 2 como base para toda nuestra programación. Tenemos que sacarnos de la cabeza el sistema decimal y adoptar el binario y/o hexadecimal. Así, cuando hagamos por ejemplo un acumulador para tomar un valor promedio, no pensemos en usar 10 muestras y luego dividir por 10 para tener el promedio. Pensemos siempre en usar un número potencia de 2 para tomar las muestras, por ejemplo 8 o 16, y después el valor promedio se saca dividiendo por 8 o por 16, que es lo mismo que hacer una rotación del acumulador 3 veces hacia la derecha para el caso de la división por 8, o 4 veces a la derecha para el caso de la división por 16.
Incluso los compiladores mas avanzados, tienen opciones de optimización. Cuando colocamos la operación A = B / 8. Ya conoce el truco, y automáticamente en vez de hacer una operación división estándar, aplica desplazamientos hacia la derecha. Lo único que complica también en éste caso es cuando la variable B es del tipo signed. El compilador tiene que respetarlo, y eso implica mayor cantidad de código al momento de compilarlo. Por ello, es importante conocer que tipo de variable estamos utilizando, si es con signo o nó, si los valores que vamos a utilizar son siempre positivos o pueden aparecer valores negativos.
Si lo que les tira más en su carrera de la programación van a ser los DSP, o van a apuntar a tener equipos donde estén ingresando datos a una tasa de velocidad relativamente importante para un microcontrolador (que puede ser del orden de las 10000 muestras por segundo), y a esas muestras hay que aplicarle algún filtro digital, donde hay que acumular valores y efectuar multiplicaciones, van a tomarle mucho cariño al uso de los desplazamientos que le van a ayudar enormemente a encontrar la solución. En mi experiencia, he hecho cosas con PIC que para muchos eruditos en la programación y manejo de microcontroladores es totalmente imposible. Sin embargo, usando las técnicas y trucos que les estoy dando se pueden implementar. Conclusión, a lo mejor con un microcontrolador económico se pueden realizar tareas que a simple vista parecen muy difíciles o imposibles. Dichas tareas sin dudas que con un microcontrolador más grande o DSP se realizan sin dificultad. Pero, cuando hay que hacer producciones en masa, el precio del microcontrolador que elijan será determinante en el precio del producto final. Por lo tanto, hay que analizar muy bien como implementar la solución para que el resultado sea el óptimo. Si no hay restricciones de microcontrolador y pueden elegir alguno bien potente elijan ese. Ahora, si existe una imposición que se deben reducir los costos al máximo, van a tener que recurrir a herramientas como éstas para poder meter en lugares mas chicos las soluciones. Tengamos en cuenta que cuando trabajamos en el mundo de los microcontroladores es totalmente diferente al mundo de las PC. En las PC cada vez se hacen programas más pesados total cada vez las computadoras son más potentes. En los microcontroladores lo que se busca principalmente es la reducción de costos. Si bien cada día aparece un microcontrolador nuevo más potente, debemos estar atentos a lo que nos ofrece la tecnología y cual es nuestro objetivo de costo.