Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


4.9.3a  Operadores de bits: tecnicismos y ejemplos adicionales

Nota:  para comprobar los resultados que se indican y completar sus propios ejemplos, puede utilizar el programa de análisis de patrón de bits de la Librería de Ejemplos ( 9.4).

§1  Introducción

Aún suponiendo que el lector no vaya a dedicarse a esto de la programación y lo haga solo esporádicamente, es más que probable que antes o después tenga que habérselas con el manejo de bits individuales.  Eso sin contar con que su utilización es constante en determinados entornos. Por ejemplo, cuando se programan sistemas embebidos ("Embedded Systems"); interfaces analógico-digitales; sistemas de comunicaciones; de adquisición de datos, Etc. Incluimos aquí algunos consejos, trucos y tecnicismos que le ayudarán a familiarizarse con el manejo de bits, algo por lo demás bastante sencillo cuando se dominan un par de trucos básicos.

Nota: en el lenguaje informático es frecuente utilizar la palabra "setear" (del inglés "set") para indicar que uno, o todos los bits de una palabra, se ponen a uno, y limpiar ("Clear") para señalar que se ponen a cero. Aunque no está recogido en el diccionario de la Academia Española de la Lengua, utilizamos este anglicismo (setear) por ser más breve que "poner a uno". 

§2  Construir un patrón de bits

Es muy frecuente que las manipulaciones de bits requieran la construcción de un patrón determinado ("bitmask"), que sirva como plantilla para comparaciones u operando de una expresión. Por supuesto, la forma más directa es escoger un número de longitud adecuada y echar mano de nuestros conocimientos de álgebra binaria para calcular el valor correspondiente del patrón deseado. Por ejemplo, necesitamos una plantilla de 16 bits con el siguiente aspecto:

11011000 00010000

Podemos "sacar la cuenta" y llegar a la conclusión de que nuestra declaración para el número debe ser:

unsigne int X = 55312;

Cuando el patrón es más largo, 32 o 64 bits, el cálculo puede ser muy tedioso. Por ejemplo:

11111010 01011000 11011100 01100110

En estos casos no es necesario pasar el resto de la tarde para encontrar que corresponde exactamente con el unsigned long 4200127590. Una alternativa es calcular el valor individual de cada octeto y componer el resultado utilizando el operador OR inclusivo y los desplazamientos adecuados. En nuestro caso, el valor de los octetos (de izquierda a derecha) es: 250, 88, 220 y 102. El número X correspondiente a esa plantilla de bits es el siguiente:

unsigned long X = (250UL << 24) | (88UL << 16) | (220UL << 8) | 102UL;


En la tabla adjunta se muestran algunos patrones de bits de uso frecuente.

Constante Patrón de bits
(unsigned char) 0x1 00000001
(unsigned char) 0x2 00000010
(unsigned char) 0x3 00000011
(unsigned char) 0x4 00000100
(unsigned char) 0x7 00000111
(unsigned char) 0x8 00001000
(unsigned char) 0xF 00001111
(unsigned char) 0x10 00010000
(unsigned char) 0x20 00100000
(unsigned char) 0x40 01000000
(unsigned char) 0x80 10000000
(unsigned short) 0xF 00000000 00001111
(unsigned int) 0xF 00000000 00000000 00000000 00001111
(unsigned char) 0xFF 11111111
(unsigned short) 0xFF 00000000 11111111
(unsigned int) 0xFF 00000000 00000000 00000000 11111111
(unsigned short) (0xFF << 8) 11111111 00000000
(unsigned int) (0xFF << 8) 00000000 00000000 11111111 00000000
(unsigned int) (0xFF << 16) 00000000 11111111 00000000 00000000
(unsigned int) (0xFF << 24) 11111111 00000000 00000000 00000000
(unsigned short) 0xFFF 00001111 11111111
(unsigned short) 0xFFFF 11111111 11111111
(unsigned int) 0xFFF 00000000 00000000 00001111 11111111
(unsigned int) 0xFFFF 00000000 00000000 11111111 11111111
(unsigned int) (0xFF | 0xFF << 8 | 0xFF << 16) 00000000 11111111 11111111 11111111
(unsigned int) (0xFF | 0xFF << 8 | 0xFF << 24) 11111111 00000000 11111111 11111111
(unsigned int) (0xFF | 0xFF << 16 | 0xFF << 24) 11111111 11111111 00000000 11111111
(unsigned int) (0xFF << 8 | 0xFF << 16 | 0xFF << 24) 11111111 11111111 11111111 00000000
§3  Invertir una zona de un patrón

Supongamos que tenemos un patrón arbitrario de cualquier longitud. Por ejemplo 32 bits y queremos obtener un patrón igual al anterior, con la condición de que sea igual al original en una zona, y que el resto sea precisamente el inverso (los bits que están a 1 pasan a ser 0 y viceversa). Para concretarlo en un ejemplo, supongamos que en un momento dado, una tarjeta de adquisición presenta en uno de sus registros un valor X de 32 bits con el siguiente aspecto:

11101110 01111001 11101001 01101001

Queremos obtener el número cuyo patrón es igual que el anterior en el primer octeto; el resto serán los inversos de los originales. Es decir, el número Y correspondiente a:

11101110 10000110 00010110 10010110

Para obtener el resultado, construimos una máscara M que tenga los bits a respetar iguales a cero y el resto (los que cambiarán de valor) en uno. En nuestro caso sería

00000000 11111111 11111111 11111111

El valor de Y puede obtenerse mediante el OR exclusivo entre el valor original y la máscara:

Y = X ^ M;

En nuestro caso:

unsigned int Y = X ^ (0xFF | 0xFF << 8 | 0xFF << 16);

Teniendo en cuenta que en el ejemplo es X == 4000967017 puede hacer la comprobación pertinente; lo mismo con cualquier otro valor de 32 bits que se utilice como valor X de entrada.

§4  Aislar una zona de un patrón

El caso es análogo al anterior, pero ahora queremos que los bits de la zona que no se conserva sean puestos a cero. En el caso del valor X anterior, queremos que sean puestos a cero todos los bits, excepto los del primer octeto, así que el resultado Y debe tener el siguiente aspecto:

11101110 00000000 00000000 00000000

El valor Y deseado puede obtenerse mediante la expresión:

Y = (X ^ M) & X;

donde M es la máscara del ejemplo anterior (tiene a 1 los bits a proteger y a 0 los bits a modificar). En el ejemplo propuesto el valor Y responde a la expresión siguiente:

unsigned int Y = (X ^ (0xFF | 0xFF << 8 | 0xFF << 16)) & X;


Si por el contrario, queremos que el resultado Y contenga unos, en vez de ceros, en la zona que no se conserva:

11101110 11111111 11111111 11111111

La expresión a utilizar sería:

unsigned int Y = (X ^ (0xFF | 0xFF << 8 | 0xFF << 16)) & (~X);

§5  Setear los valores de una máscara en un patrón

El problema se plantea en los siguientes términos: sean dos máscaras M y P; queremos una máscara resultante R igual al patrón M, con la salvedad de que los bits que estuvieran seteados en la máscara P (a 1), lo estén también en R. El resultado puede obtenerse mediante:

R = M | P;

Si lo que queremos es modificar M en el sentido indicado, la expresión es:

M |= P;

§6  Limpiar los valores de una máscara en un patrón

El problema es inverso al anterior: queremos que la resultante R sea igual a la máscara M, con la salvedad de que los bits que estuvieran seteados en P, sean limpiados (puestos a 0) en R.

R = M & (~P);

Si lo que queremos es modificar M en el sentido indicado, la expresión es:

M &= ~P;

§7  Comprobar si un patrón encaja en una máscara

El problema consiste en verificar si los bits seteados (que están a 1) en un patrón P tienen correspondencia con los bits correspondientes de una patrón M (también están a 1 en M). La verificación cierto/falso, viene determinado por la expresión  (M & P). Por ejemplo:

if ( M & P) ...    // Encaja
else        ...    // No encaja


En las aplicaciones reales, es frecuente que los patrones y las máscaras estén identificados mediante constantes cuyos valores se incluyen en "defines". Por ejemplo, en la programación Windows, el estilo de una ventana está definido por un valor entero en el que cada uno de sus bits tiene un significado y efecto concreto. El programador puede componer un estilo particular combinando los componentes individuales que están definidos mediante constantes. Por ejemplo:

DWORD style = WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_WANTRETURN |
              ES_AUTOVSCROLL | ES_NOHIDESEL,


Como indicábamos en la página anterior, DWORD es un typedef cuya traducción exacta depende de la plataforma, pero que probablemente será traducida a un unsigned long.  A su vez, las constantes a la derecha (WS_CHILD | WS_VISIBLE | WS_VSCROLL, etc.) que determinan los distintos componentes del estilo, están definidas en los ficheros de cabecera de forma que son DWORDs cuyos valores tienen un bit a uno y los demás a cero, por lo que son múltiplos de 2 (dentro de la serie  1,  2,  4,  8, 16,  32,  64, etc). Al programador no le interesan sus valores concretos, solo sus nombres, que por lo demás son bastante descriptivos.  Algunas características del estilo pueden ser modificadas en runtime después de creada la ventana, de forma que son normales trozos de código como el que sigue:

DWORD style = GetWindowLong(hwnd, GWL_STYLE);  // se obtiene el estilo de una ventana
switch(mode) {    // mode depende de una orden de usuario
    /* ... */
    case EDIT_UPPER:    // orden de texto solo en mayúsculas
        style &= ~ ES_LOWERCASE;
        style |= ES_UPPERCASE;
        break;
    case EDIT_LOWER:    // orden de texto solo en minúsculas
        style &= ~ ES_UPPERCASE;
        style |= ES_LOWERCASE;
        break;
    case EDIT_NONE:    // orden contenido de ventana no editable
        style &= ~(ES_UPPERCASE | ES_LOWERCASE);
        break;
}
SetWindowLong(hwnd, GWL_STYLE, style);    // establecer nuevo estilo de ventana


§8  Simplificación de expresiones lógicas

Es frecuente que en la programación de interfaces analógico/digitales; tarjetas de adquisición de datos; dispositivos embebidos; sistemas de comunicación y demás parafernalia electrónica del tipo citado en la introducción de este capítulo, se utilicen expresiones lógicas que pueden resultar bastante complicadas. De forma similar a cómo en una expresión del tipo a = b + 2c + 1.5b -4c -3 podemos simplificar y poner finalmente que a = 2.5b -2c -3, el álgebra lógica permite utilizar ciertas identidades que pueden ayudarnos a la simplificación de las expresiones resultantes. En la tabla adjunta se muestran algunas de estas reglas [1].

!(!x) == x

x | x == x

x | !x == true

!x | !y == !(x & y)               // Teorema de DeMorgan 

!x & !y == !(x | y)               // Teorema de DeMorgan 

x & x == x 

x & !x == false 

x | y == y | x                    // propiedad conmutativa 

x & y == y & x                    // propiedad Conmutativa 

(x | y) | z == x | (y | x)        // propiedad Asociativa 

(x & y) & z == x & (y & z)        // propiedad Asociativa 

x & y | x & z == x & (y | z)      // propiedad Distributiva 

(x | y) & (x | x) == x | (y & z)  // propiedad Distributiva 

x | x & y == x 

x & y | x & !y == x

(x & y) | (!x & z) | (y & z) == (x & y) | (!x & z) 

(x | y) & (!x | z) & (y | z) == (x | y) & (!x | z) 

x & (x | y) == x 

(x | y) & (x | !y) == x

  Inicio.


[1]  Del artículo "Avoid Boolean Overload" de Jay Miller en Codeguru.com. Reproducido con autorización del autor.