Desde hace mucho tiempo, los humanos hemos adoptado el sistema de numeración en base 10, que utiliza cifras del 0 al 9 para representar números. Cada cifra tiene una posición que determina su valor, como las unidades, decenas, centenas, y así sucesivamente. Sin embargo, cuando programamos, aunque escribamos un número decimal como 45, internamente el ordenador no lo almacena así. Los ordenadores trabajan en base 2, es decir, en sistema binario, que solo usa dos cifras: 0 y 1.
Esto significa que un número como 45 se representa en memoria como una secuencia de bits, por ejemplo, 00101101. Normalmente, la memoria se organiza en celdas de 8 bits, lo que permite representar 256 valores diferentes. Pero hay memorias con tamaños distintos, como 16 o 32 bits, y por eso es importante elegir bien el tipo de dato en C, ya que cada tipo define cuántos bits se usan para almacenar un número y, por tanto, el rango de valores que puede contener.
En C contamos con operadores binarios que nos permiten manipular directamente estos bits. Los principales son NOT, AND, OR y XOR. Para entenderlos, partimos de un número binario, por ejemplo, 10011100, que en decimal es 156. Cada bit tiene una posición, desde la 7 (bit más significativo o MSB) hasta la 0 (bit menos significativo o LSB).
El operador NOT es un operador unario que invierte cada bit del número: donde había un 1 pone un 0, y donde había un 0 pone un 1. Por ejemplo, el NOT de 10011100 es 01100011, que equivale a 99 en decimal. En C, se usa la tilde ~ para aplicar este operador. Si declaramos una variable foo como un signed char con valor 156 y aplicamos ~foo, el resultado puede parecer extraño, como -157, debido a cómo se interpreta el tipo y el tamaño de la variable. Esto se debe a que el operador NOT trabaja con enteros de tamaño predeterminado (normalmente 32 bits), y para obtener el resultado esperado debemos hacer un cast explícito a signed char para limitar la operación a 8 bits.
signed char foo = 156;
printf("%d\n", foo); // Imprime 156
printf("%d\n", (signed char)~foo); // Imprime 99
Los operadores AND, OR y XOR trabajan con dos números binarios. Por ejemplo, tomemos A = 10111001 (185 decimal) y B = 00111001 (52 decimal).
El operador AND (& en C) produce un bit 1 solo si ambos bits correspondientes en A y B son 1. En nuestro ejemplo, A & B da como resultado un número con solo dos bits en 1, que es 48 en decimal.
El operador OR (| en C) produce un bit 1 si al menos uno de los bits correspondientes en A o B es 1. Así, A | B da 189 en decimal.
El operador XOR (^ en C) produce un bit 1 solo si exactamente uno de los bits correspondientes es 1, pero no ambos. En nuestro caso, A ^ B da 141 en decimal.
unsigned char A = 185; // 10111001
unsigned char B = 52; // 00111001
printf("%d\n", A & B); // Imprime 48
printf("%d\n", A | B); // Imprime 189
printf("%d\n", A ^ B); // Imprime 141
Estos operadores son fundamentales para manipular bits y se usan para crear máscaras de bits, que permiten activar, desactivar o consultar bits específicos dentro de un número. Además, combinados con operadores de desplazamiento, ofrecen herramientas muy potentes para optimizar y controlar datos a nivel binario en C y otros lenguajes.
Es importante familiarizarse con la representación binaria y la lógica detrás de estos operadores para aprovechar al máximo su potencial en programación. Así, podremos realizar operaciones eficientes y precisas que van más allá de la simple aritmética decimal.