domingo, 18 de noviembre de 2012

Operadores y Máscaras de Bits

Las computadoras almacenan datos en forma de 0s y 1s. La razón de ésto es que el binario es el sistema de numeración posicional más simple. Un bit es la menor cantidad de información que se puede almacenar. Un conjunto de 8 bits recibe el nombre de palabra o byte. El lenguaje de programación C es un lenguaje de nivel medio. Ésto, entre otras cosas, significa que es posible realizar operaciones a nivel de bits. Ésta es una interacción directa con el hardware y por lo tanto es mucho más rápida que la programación a alto nivel. Por ejemplo, es posible determinar el tamaño en bytes de cualquier tipo de datos con
=====================================================================================
El operador unario sizeof()
=====================================================================================
Vamos a considerar el siguiente programa

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *                                                         +
 * Este programa imprime los tamanios en bits de variables +
 * tipo char, int, double, etc.                            + 
 *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

#include 
#define TAMANO 2

int main()
{  /* Abre main*/
char c;
int x;
int Arreglo[TAMANO];
double y;

printf("\nEl tamanio en bytes de una variable char es: %d\n", sizeof(c));
printf("El tamanio en bytes de una variable int es: %d\n", sizeof(x));
printf("El tamanio en bytes de una arreglo int de %d localidades es: %d", TAMANO, sizeof(Arreglo));
printf("\nEl tamanio en bytes de una variable double es: %d\n", sizeof(y));

return 0;
}  /* Cierra main*/

La ejecución del mismo produce

El tamanio en bytes de una variable char es: 1
El tamanio en bytes de una variable int es: 4
El tamanio en bytes de una arreglo int de 2 localidades es: 8
El tamanio en bytes de una variable double es: 8
Cuando el operador sizeof se aplica a una variable char, el resultado es 1 porque 1 byte (8 bits) es el tamaño necesario para almacenar un carácter cualquiera de los 256 que forman el código ASCII. Así que por esa razón el tamaño de una variable tipo carácter es estándar. El tamaño de un entero en C, depende de la máquina en la que se ejecuta; para ésta máquina en particular es de 4 bytes (32 bits) (véase la entrada La representación en complemento a 2 para más detalles.) También se ha definido un arreglo de 2 localidades enteras; el número de bytes que ocupa dicho arreglo es 8, cuatro para cada localidad entera. Adicionalmente aparece una variable tipo double, que ocupa 8 bytes (64 bits).
=====================================================================================
& El Operador AND de Bits
=====================================================================================
El operador & (NO confundir con el operador lógico &&) es un operador lógico a nivel de bits. Compara un par de cadenas de bits bit por bit, el resultado para cada comparación es 1 si los dos bits son 1 y 0 en otro caso. Como ejemplo, vamos a considerar las siguientes cadenas:
Una máscara de bits
Empezando por la derecha, el primer bit de la primera cadena, tiene un 1, y la segunda cadena tiene también un 1, por lo tanto, 1 & 1 = 1. Aplicando & a los bits de la siguiente posición: 0 & 0 = 0, los siguientes: 1 & 0 = 0; después 0 & 1 = 0; los bits de la 5ta posición: 1 & 1 = 1, y los últimos dan 0, 1 & 0 = 0.
Con el operador & se puede ocultar un conjuto de bits que no son relevantes en determinada situación. Ésto constituye una máscara de bits. Vamos a considerar un pequeño problema y elaborar una solución: En un ciclo controlado por una variable llamada TAMANO, es necesario imprimir el valor del contador sólo desde 0 hasta 15, y comenzar después en 0, aún cuando TAMANO se siga incrementando. Con algunas instrucciones condicionales dentro del ciclo es posible realizar esta tarea, pero con una máscara de bits, se puede escribir un programa como el siguiente:

/*+++++++++++++++++++++++++++++++++++++++++++++
 *                                            +
 * Este programa imprime ciclicamente valores +
 * de una variable por debajo de un limite,   +
 * por medio de una mascara de bits.          +
 * +++++++++++++++++++++++++++++++++++++++++++*/

#include 
#define TAMANO 200
#define LIMITE 15

int  main()

{ /* Abre main*/
int i = 0;

for ( i = 0; i < TAMANO; i++ )
{
 printf("%d\n", i & LIMITE);
}

return 0;
} /* Cierra main*/

La salida de este programa es: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, ...
Se ha mantenido la impresión por debajo de la variable LIMITE. La línea relevante, para los fines de esta entrada, es la siguiente:

printf("%d\n", i & LIMITE);

la función printf imprime un entero, el entero que se produce de la comparación de los bits de el primer número, el contador i, con el número LIMITE. Es importante mencionar que éste y los siguientes operadores son aplicables únicamente a variables de tipo entero, como char, int, long, short, con los calificativos signed y unsigned. En éste caso, la variable límite funciona como una máscara, que oculta todos los bits a la izquierda de el último 1 a la izquierda.
Máscara de bits para controlar un ciclo.
Ésta figura presenta los primeros 6 bits de los los números LIMITE (15) y un valor de TAMANO (25), recuerde que, en nuestra máquina, los enteros tienen tìpicamente 32 bits, o sea que hacia la izquierda de cada renglón se encuentran muchos ceros que no se han dibujado por comodidad. Aplicando el operador & bit por bit a ambas representaciones obtenemos el renglón de abajo, el cual es el número 9 en el sistema de numeración binario. Observe que más allá del último 1 (el de la extrema izquierda)de la máscara (15) todos los bits del resultado se vuelven 0, y si hubiera algún 0 antes del último 1 ocurriría una ambigüedad, ya que 0 ó 1 en TAMANO produciría 0. ÉSta es la razón por la cual éste método funciona solamente con números que en su representación binaria tengan sólo 1s, como 3 (11), 7 (111), 15 (1111), 31 (11111), etc.
=====================================================================================
<< El Operador de desplazamiento izquierdo de Bits
=====================================================================================
Vamos a considerar el siguiente programa.

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*  Este programa hace uso de el operador de desplazamiento *
*  hacia la izquierda <<                                   *
*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

#include 
using namespace::std;

int main()
{   /* Abre main*/
int x = 1;
// Los operadores de bits solo se aplican a variables unsigned
cout <<"\nEl tamanio de una variable int es: "<< sizeof(int)<< endl;

cout <<"\nEl valor inicial de x: "<< x << endl;

for (unsigned i = 1; i <= 8; i++ ) 
{
  
unsigned desplazado = x << i;

cout <<"\nEl numero: " << desplazado << endl;
}

cout <<"\nEl valor final de x: " << x << endl;
}   /* Cierra main*/


La ejecución es la siguiente:

El tamanio de una variable int es: 4

El valor inicial de x: 1
El numero: 2
El numero: 4
El numero: 8
El numero: 16
El numero: 32
El numero: 64
El numero: 128
El numero: 256
El valor final de x: 1

Al principio se define una variable llamada x, la cual se inicializa con el valor 1. Un ciclo for, de 1 a 8 incluidos, utiliza el operador de desplazamiento izquierdo para recorrer los bits hacia la izquierda, lo cual, en el sistema posicional binario, hace que el valor se incremente en potencias de 2, tal como se muestra en la figura siguiente:

Observe que el valor de x, fuera del ciclo for, sigue siendo de 1.

=====================================================================================
>> El operador de desplazamiento derecho de bits
=====================================================================================
De manera similar, el lenguaje C proporciona el operador de desplazamiento derecho de bits >>. El siguiente programa muestra cómo opera:

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*  Este programa hace uso de el operador de desplazamiento *
*  hacia la derecha  >>                                    *
*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

#include 
using namespace::std;

int main()
{   /* Abre main*/
int x = 128;

cout <<"\nEl tamanio de una variable int es: "<< sizeof(int)<< endl;

cout <<"\nEl valor inicial de x: "<< x << endl;

for (unsigned i = 1; i <= 8; i++ ) 
{
  
unsigned desplazado = x >> i;

cout <<"\nEl numero: " << desplazado << endl;
}

cout <<"\nEl valor final de x: " << x << endl;
}   /* Cierra main*/

La salida es la siguiente:

El tamanio de una variable int es: 4

El valor inicial de x: 128

El numero: 64
El numero: 32
El numero: 16
El numero: 8
El numero: 4
El numero: 2
El numero: 1
El numero: 0
El valor final de x: 128

Se inicia con una variable llamada x, la cual se inicializa en 128, en un ciclo for se va disminuyendo el valor de dicha variable por medio del operador de desplazamiento derecho. La variable adquiere sucesivamente los valores 128, 64, 32, 16, 8, 4, 2, 1. Una representación esquemática del acarreo de bits es la siguiente:


2 comentarios:

  1. Oye, muy bueno el post, los ejemplos muy claros. Me ayudo cantidaad!!

    ResponderEliminar

Related Posts Plugin for WordPress, Blogger...