Operatori Bitwise — Ripasso

OperatoreNomeEsempioRisultato
&AND0b1010 & 0b11000b1000 (8)
|OR0b1010 | 0b11000b1110 (14)
^XOR0b1010 ^ 0b11000b0110 (6)
~NOT~0b10100b0101... (-11 in int32)
<<Left shift0b0001 << 30b1000 (8 = 1×2³)
>>Right shift0b1000 >> 20b0010 (2 = 8÷4)

Operazioni su Singoli Bit

CSet, Clear, Toggle, Check di un bit
#include <stdio.h>

// SET bit i (porta a 1)
#define SET_BIT(n, i)    ((n) |= (1 << (i)))

// CLEAR bit i (porta a 0)
#define CLEAR_BIT(n, i)  ((n) &= ~(1 << (i)))

// TOGGLE bit i (inverte)
#define TOGGLE_BIT(n, i) ((n) ^= (1 << (i)))

// CHECK bit i (1 se settato, 0 altrimenti)
#define CHECK_BIT(n, i)  (((n) >> (i)) & 1)

void stampa_binario(unsigned int n, int bits) {
    for (int i=bits-1; i>=0; i--) {
        printf("%d", (n >> i) & 1);
        if (i % 4 == 0 && i > 0) printf("_");
    }
}

int main() {
    unsigned int x = 0b0000_1010;  // C++14: binary literals con separatori

    printf("Iniziale: "); stampa_binario(x, 8); printf(" (%d)\n", x);

    SET_BIT(x, 0);
    printf("Set bit0: "); stampa_binario(x, 8); printf(" (%d)\n", x);

    CLEAR_BIT(x, 1);
    printf("Clear b1: "); stampa_binario(x, 8); printf(" (%d)\n", x);

    TOGGLE_BIT(x, 3);
    printf("Toggle 3: "); stampa_binario(x, 8); printf(" (%d)\n", x);

    printf("Bit 2 = %d\n", CHECK_BIT(x, 2));
    printf("Bit 3 = %d\n", CHECK_BIT(x, 3));

    // Bitmask per permessi (stile Unix)
    #define READ    0b100  // 4
    #define WRITE   0b010  // 2
    #define EXECUTE 0b001  // 1

    int permessi = 0;
    permessi |= READ;    // Aggiungi lettura
    permessi |= WRITE;   // Aggiungi scrittura
    printf("\nPermessi: %d (rwx = %d%d%d)\n",
           permessi,
           !!(permessi & READ),
           !!(permessi & WRITE),
           !!(permessi & EXECUTE));

    permessi &= ~WRITE;  // Rimuovi scrittura
    printf("Senza W: %d (r-x = %d%d%d)\n",
           permessi,
           !!(permessi & READ),
           !!(permessi & WRITE),
           !!(permessi & EXECUTE));

    return 0;
}

Trucchi sui Bit

CTrucchi comuni — interviewing classics
#include <stdio.h>

// Verifica se n è potenza di 2
// Potenze di 2 hanno esattamente 1 bit a 1: es. 8=1000, 16=10000
// n & (n-1) azzera il bit meno significativo: se n è pwr2 → risulta 0
int e_potenza_di_2(int n) { return n > 0 && (n & (n-1)) == 0; }

// Swap senza variabile temporanea (con XOR)
// a = a^b, b = a^b = a^b^b = a, a = a^b = a^b^a = b
void swap_xor(int *a, int *b) { *a ^= *b; *b ^= *a; *a ^= *b; }

// Conta i bit a 1 (population count)
int popcount(unsigned int n) {
    int count = 0;
    while (n) { count += n & 1; n >>= 1; }
    return count;
}
// Versione veloce (Brian Kernighan)
int popcount_fast(unsigned int n) {
    int c=0; while(n) { n &= n-1; c++; } return c;
    // n &= n-1 azzera il bit meno significativo ogni iterazione
}
// Versione built-in GCC (una sola istruzione CPU)
int popcount_gcc(unsigned int n) { return __builtin_popcount(n); }

// Trova il bit meno significativo (LSB)
int lsb(int n) { return n & (-n); }  // -n = ~n + 1 (complemento a 2)

// Conta gli zeri iniziali (leading zeros)
int leading_zeros(unsigned int n) { return n ? __builtin_clz(n) : 32; }

// Floor(log2(n)) usando bit tricks
int floor_log2(unsigned int n) { return 31 - __builtin_clz(n); }

// Inverti i bit di un byte
unsigned char inverti_byte(unsigned char b) {
    b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;  // Scambia nibble
    b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
    b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
    return b;
}

// Numero con tutti i bit a 1 nei primi n bit
unsigned int maschera_n_bit(int n) { return (1u << n) - 1; }

// Estrai n bit a partire dalla posizione p
int estrai_bit(int num, int p, int n) {
    return (num >> p) & ((1 << n) - 1);
}

int main() {
    printf("Potenze di 2: ");
    for (int i=0; i<20; i++) if (e_potenza_di_2(i)) printf("%d ", i);
    printf("\n");

    int a=15, b=27;
    swap_xor(&a, &b);
    printf("Swap XOR: a=%d b=%d\n", a, b);

    printf("popcount(255) = %d\n", popcount(255));    // 8
    printf("popcount(256) = %d\n", popcount(256));    // 1
    printf("popcount(127) = %d\n", popcount_fast(127)); // 7

    printf("LSB(12) = %d  (12=1100, LSB=4=100)\n", lsb(12));
    printf("leading_zeros(1) = %d\n", leading_zeros(1));    // 31
    printf("floor_log2(16) = %d\n", floor_log2(16));        // 4
    printf("floor_log2(100) = %d\n", floor_log2(100));      // 6

    printf("Maschera 4 bit: %d (= %X)\n", maschera_n_bit(4), maschera_n_bit(4));  // 15, F
    printf("Estrai bit 2-4 di 0b11010110: %d\n", estrai_bit(0b11010110, 2, 3));   // 101=5

    return 0;
}

Applicazioni Pratiche

CBitset, Flags, Codifica con bit
#include <stdio.h>
#include <string.h>

// ===== Bitset: insieme di interi compresso in bit =====
// Invece di int presente[1000000] → usiamo unsigned int bs[1000000/32]
#define BS_SIZE 1024

typedef unsigned int Bitset[BS_SIZE / 32 + 1];

void bs_set(Bitset bs, int i)    { bs[i/32] |= (1u << (i%32)); }
void bs_clear(Bitset bs, int i)  { bs[i/32] &= ~(1u << (i%32)); }
int  bs_check(Bitset bs, int i)  { return (bs[i/32] >> (i%32)) & 1; }

// Crivello di Eratostene con bitset
void crivello(int max) {
    Bitset non_primo = {0};
    printf("Numeri primi fino a %d: ", max);
    for (int i=2; i<=max; i++) {
        if (!bs_check(non_primo, i)) {
            printf("%d ", i);
            for (int j=i*i; j<=max; j+=i) bs_set(non_primo, j);
        }
    }
    printf("\n");
}

// ===== Flags in stile enum bitfield =====
typedef enum {
    OPZIONE_NESSUNA   = 0,
    OPZIONE_VERBOSE   = 1 << 0,  // 1
    OPZIONE_DEBUG     = 1 << 1,  // 2
    OPZIONE_LOG_FILE  = 1 << 2,  // 4
    OPZIONE_QUIET     = 1 << 3,  // 8
    OPZIONE_FORCE     = 1 << 4,  // 16
} Opzioni;

void stampa_opzioni(Opzioni o) {
    if (o & OPZIONE_VERBOSE)  printf("  verbose ");
    if (o & OPZIONE_DEBUG)    printf("  debug ");
    if (o & OPZIONE_LOG_FILE) printf("  log_file ");
    if (o & OPZIONE_QUIET)    printf("  quiet ");
    if (o & OPZIONE_FORCE)    printf("  force ");
    printf("\n");
}

// ===== Trucco: verifica se int ha tutti bit uguali =====
int tutti_uguali(int n) {
    return n == 0 || n == ~0;
}

int main() {
    crivello(50);

    // Flags
    Opzioni opt = OPZIONE_VERBOSE | OPZIONE_DEBUG | OPZIONE_LOG_FILE;
    printf("Opzioni attive:"); stampa_opzioni(opt);

    opt &= ~OPZIONE_DEBUG;  // Rimuove DEBUG
    printf("Senza debug:"); stampa_opzioni(opt);

    opt ^= OPZIONE_QUIET;   // Toggle QUIET
    printf("Toggle quiet:"); stampa_opzioni(opt);

    printf("Sono attive verbose E log_file? %s\n",
           (opt & (OPZIONE_VERBOSE | OPZIONE_LOG_FILE)) == (OPZIONE_VERBOSE | OPZIONE_LOG_FILE)
           ? "Sì" : "No");

    return 0;
}
🏋️ Esercizi
1
Trova l'unico elemento
FacileXOR

Dato un array dove ogni elemento appare due volte tranne uno, trovalo in O(n) e O(1) spazio usando XOR. Perché funziona? (hint: a XOR a = 0, a XOR 0 = a). Poi: variante dove ogni elemento appare tre volte tranne uno — usa operazioni bit per bit su ogni bit.

C
#include <stdio.h>

// Appare 2 volte tranne uno: XOR di tutto
int solo_una_volta(int arr[], int n) {
    int result = 0;
    for(int i=0;i<n;i++) result ^= arr[i];
    return result;
    // Spiegazione: tutti i pari si annullano con XOR, rimane solo l'unico
}

// Appare 3 volte tranne uno: contare i bit modulo 3
int solo_una_volta_v2(int arr[], int n) {
    int result = 0;
    for(int bit=0;bit<32;bit++){
        int sum=0;
        for(int i=0;i<n;i++) sum += (arr[i]>>bit)&1;
        if(sum%3 != 0) result |= (1<<bit);
    }
    return result;
}

int main() {
    int arr1[] = {4,1,2,1,2};
    printf("Unico (paia): %d\n", solo_una_volta(arr1, 5));  // 4

    int arr2[] = {2,2,3,2};
    printf("Unico (triple): %d\n", solo_una_volta_v2(arr2, 4));  // 3

    int arr3[] = {0,1,0,1,0,1,99};
    printf("Unico (triple): %d\n", solo_una_volta_v2(arr3, 7));  // 99
    return 0;
}
2
Manipolazione della griglia
MedioBitboard

Rappresenta una griglia 8x8 (come una scacchiera) con un singolo uint64_t (un bit per cella). Implementa: set/clear/toggle/check di una cella (r,c), stampa la griglia, conta le celle occupate, e verifica se due griglie si sovrappongono.

C
#include <stdio.h>
#include <stdint.h>

typedef uint64_t Griglia;  // 64 bit = 8×8

// Bit (r,c) è nella posizione r*8+c
#define CELLA(r,c) ((Griglia)1 << ((r)*8+(c)))

Griglia g_set(Griglia g, int r, int c)    { return g | CELLA(r,c); }
Griglia g_clear(Griglia g, int r, int c)  { return g & ~CELLA(r,c); }
Griglia g_toggle(Griglia g, int r, int c) { return g ^ CELLA(r,c); }
int     g_check(Griglia g, int r, int c)  { return !!(g & CELLA(r,c)); }

void g_stampa(Griglia g) {
    printf("  01234567\n");
    for(int r=0;r<8;r++){
        printf("%d ", r);
        for(int c=0;c<8;c++) printf("%c", g_check(g,r,c)?'X':'.');
        printf("\n");
    }
}

int g_conta(Griglia g) { return __builtin_popcountll(g); }
int g_sovrappone(Griglia a, Griglia b) { return (a & b) != 0; }

int main() {
    Griglia g = 0;
    // Posiziona alcune pezzi
    g = g_set(g, 0, 0); g = g_set(g, 0, 7);
    g = g_set(g, 7, 0); g = g_set(g, 7, 7);
    g = g_set(g, 3, 3); g = g_set(g, 3, 4);
    g = g_set(g, 4, 3); g = g_set(g, 4, 4);

    g_stampa(g);
    printf("Celle occupate: %d\n", g_conta(g));

    Griglia g2 = 0;
    g2 = g_set(g2, 3, 3);  // Sovrappone con g
    printf("Si sovrappongono: %s\n", g_sovrappone(g,g2)?"Sì":"No");
    return 0;
}