Operadores a nivel de bit - Bitwise operators

¿Qué son?:

Son operadores que no trabajan con valores "numéricos" en el sentido aritmético, sino bit por bit sobre la representación binaria de un dato entero.

Se aplican sobre:

NO APLICA a float, double, struct, etc.

Representación binaria:

Supongamos un unsigned char(8 bits):

Cada operador va a actuar bit a bit, comparando o modificando esos 0 y 1.

Operador AND bit a bit ( & )

No confundir con el AND lógico (&&)

Sintaxis:

resultado = a & b;

Regla:

Un bit vale 1 sólo si ambos bits son 1.

a b a & b
0 0 0
0 1 0
1 0 0
1 1 1

Ejemplo:


unsigned char a = 12; // 0000 1100
unsigned char b = 10; // 0000 1010
unsigned char c = a & b; // 0000 1000 -> 8

¿Para qué se usa?:

Ejemplo típico:


if (estado & 0x01) {
// el bit 0 esta en 1
}  

Operador OR bit a bit (|)

No confundir con el OR lógico (||)

Sintaxis:

resultado = a | b;

Regla:

Un bit vale 1 solo si al menos uno es 1.

a b a | b
0 0 0
0 1 1
1 0 1
1 1 1

Ejemplo:


unsigned char a = 12; // 0000 1100
unsigned char b = 10; // 0000 1010
unsigned char c = a | b; // 0000 1110 -> 24

¿Para qué se usa?:

Ejemplo típico:


estado = estado | 0x04; // pone en 1 el bit 2

Operador XOR bit a bit (^)

Este es el más "raro" al principio.

Sintaxis:

resultado = a ^ b;

Regla:

Un bit vale 1 solo si son distintos.

a b a ^ b
0 0 0
0 1 1
1 0 1
1 1 0

Ejemplo:


unsigned char a = 12; // 0000 1100
unsigned char b = 10; // 0000 1010
unsigned char c = a ^ b; // 0000 0110 -> 6

¿Para qué se usa?:

Hoy en día este método no se recomienda: es menos legible y los compiladores modernos optimizan mejor el swap tradicional con variable auxiliar.

Ejemplo típico:


estado ^= 0x01; // invierte el bit 0

Swap sin variable auxiliar:


a = a ^ b;
b = a ^ b;
a = a ^ b;

Operador NOT o Complemento a uno (~)

Sintaxis:

resultado = ~a;

Regla:

Invierte todos los bits:

a ~a
0 1
1 0

Ejemplo:


unsigned char a = 5; // 0000 0101
unsigned char b = ~a; // 1111 1010 -> 250

IMPORTANTE: En tipos con signo, el resultado depende de la representación (en C usualmente Complemento a dos). Por eso para bitwise - bit a bit conviene usar unsigned.

¿Para qué se usa?:

Ejemplo típico:


estado &= ~0x04; // pone en 0 el bit 2  

Desplazamiento a la izquierda - Shift left (<<)

Sintaxis:


resultado = a << n;

¿Qué hace?:

Desplaza los bits n posiciones a la izquierda, rellenando con ceros a la derecha.

Ejemplo:


unsigned char a = 3; // 0000 0011
unsigned char b = a << 2; // 0000 1100 => 12

Interpretación:

Equivale para unsigned a multiplicar por 2n.


a << 1 // Si a vale 1: 1 * 2^1 => 2
a << 2 // Si a vale 1: 1 * 2^2 => 4

Esta equivalencia es válida siempre que no se pierdan bits. Si el desplazamiento provoca overflow, el comportamiento puede no coincidir con la multiplicación/división aritmética.

Desplazamiento a la derecha - Shift right (>>)

Sintaxis:


resultado = a >> n;

¿Qué hace?:

Desplaza los bits n posiciones a la derecha, rellenando con ceros a la izquierda en tipos unsigned.

Ejemplo:


unsigned char a = 8; // 0000 1000
unsigned char b = a >> 2; // 0000 0010 => 2

Interpretación:

Equivale para unsigned a dividir por 2n.


a >> 1 // Si a vale 1: 1 / 2^1 => 0 (truncado)
a >> 2 // Si a vale 1: 1 / 2^2 => 0

En C, los operadores de desplazamiento trabajan sobre enteros. Cualquier división descarta la parte fraccionaria (truncamiento).

Shift right en tipos signed:

Dependiente de la implementación.

Bitwise - bit a bit vs Lógicos:

Operador Tipo Resultado
& bitwise Opera bit a bit
&& lógico 0 o 1
| bitwise Opera bit a bit
|| lógico 0 o 1
^ bitwise Opera bit a bit
! lógico Negación lógica
˜ bitwise Complemento a 1

Resumen:

  • Los operadores bitwise trabajan a nivel de bits.
  • Son ideales para flags, máscaras y control fino.
  • Para evitar sorpresas, se recomienda usar unsigned.

Teoría aplicada

Makefile:


CC = gcc
CFLAGS = -std=c11 -Wall -Wextra -pedantic

PROG = bitwise
OBJ = main.o

$(PROG): $(OBJ)
	$(CC) $(OBJ) -o $(PROG)

$(OBJ): main.c
	$(CC) $(CFLAGS) -c main.c

.PHONY: clean
clean:
	del $(OBJ) $(PROG).exe

Código:


// gcc -std=c11 -Wall -Wextra -pedantic main.c -o bitwise

#include <stdio.h>

/* Funcion auxiliar para mostrar unsigned char en binario */
void binprint(unsigned char x)
{
    for (int i = 7; i >= 0; i--)
    {
        printf("%d", (x >> i) & 1);
        if (i == 4)
            printf(" ");
    }
}

int main(void)
{
    unsigned char a = 12; // 0000 1100
    unsigned char b = 10; // 0000 1010
    unsigned char r;

    printf("Valores iniciales:\n");
    printf("a = %3u = ", a); binprint(a); printf("\n");
    printf("b = %3u = ", b); binprint(b); printf("\n\n");

    /* ---- AND bitwise ---- */
    r = a & b;
    printf("AND bitwise (a & b):\n");
    printf("%3u = ", r); binprint(r); printf("\n\n");

    /* ---- OR bitwise ---- */
    r = a | b;
    printf("OR bitwise (a | b):\n");
    printf("%3u = ", r); binprint(r); printf("\n\n");

    /* ---- XOR bitwise ---- */
    r = a ^ b;
    printf("XOR bitwise (a ^ b):\n");
    printf("%3u = ", r); binprint(r); printf("\n\n");

    /* ---- NOT bitwise ---- */
    r = ~a;
    printf("NOT bitwise (~a):\n");
    printf("%3u = ", r); binprint(r); printf("\n\n");

    /* ---- SHIFT LEFT ---- */
    r = a << 2;
    printf("Shift left (a << 2):\n");
    printf("%3u = ", r); binprint(r); printf("\n\n");

    /* ---- SHIFT RIGHT ---- */
    r = a >> 2;
    printf("Shift right (a >> 2):\n");
    printf("%3u = ", r); binprint(r); printf("\n\n");

    return 0;
}
Ejercicios de Operadores a nivel de bit