Bitwise Operators

Introduction

Bitwise operators work at the bit level and perform operations on individual bits of data. These operators are particularly useful for low-level programming, system programming, embedded systems, and optimization tasks. They manipulate data at the most fundamental level - the binary representation of numbers.

Understanding bitwise operations is crucial for memory optimization, flag manipulation, cryptography, and performance-critical applications where direct bit manipulation provides speed advantages over arithmetic operations.

Key Concepts

Bit: Smallest unit of data (0 or 1) Binary Representation: How numbers are stored as sequences of bits Bit Position: Location of a bit in a binary number (0-indexed from right) Bit Manipulation: Directly modifying individual bits in data

Binary Number System Review

#include <stdio.h>
void printBinary(int n) {
    for (int i = 7; i >= 0; i--) {
        printf("%d", (n >> i) & 1);
    }
    printf(" (%d)\n", n);
}

int main() {
    printf("Decimal -> Binary representation:\n");
    printBinary(5);   // 00000101
    printBinary(10);  // 00001010
    printBinary(15);  // 00001111
    printBinary(7);   // 00000111
    
    return 0;
}

Bitwise AND (&)

Basic AND Operation

#include <stdio.h>
int main() {
    int a = 12;  // 1100 in binary
    int b = 10;  // 1010 in binary
    int result = a & b;  // 1000 in binary (8 in decimal)
    
    printf("a = %d (binary: 1100)\n", a);
    printf("b = %d (binary: 1010)\n", b);
    printf("a & b = %d (binary: 1000)\n", result);
    
    return 0;
}

Practical Uses of AND

#include <stdio.h>
int main() {
    // Check if a number is even (last bit is 0)
    int number = 15;
    if ((number & 1) == 0) {
        printf("%d is even\n", number);
    } else {
        printf("%d is odd\n", number);
    }
    
    // Clear specific bits (masking)
    int value = 255;  // 11111111
    int mask = 240;   // 11110000 (clear last 4 bits)
    int cleared = value & mask;  // 11110000 (240)
    printf("Cleared last 4 bits: %d\n", cleared);
    
    return 0;
}

Bitwise OR (|)

Basic OR Operation

#include <stdio.h>
int main() {
    int a = 12;  // 1100 in binary
    int b = 10;  // 1010 in binary
    int result = a | b;  // 1110 in binary (14 in decimal)
    
    printf("a = %d (binary: 1100)\n", a);
    printf("b = %d (binary: 1010)\n", b);
    printf("a | b = %d (binary: 1110)\n", result);
    
    return 0;
}

Setting Specific Bits

#include <stdio.h>
int main() {
    int value = 8;    // 00001000
    int mask = 4;     // 00000100
    
    printf("Original value: %d\n", value);
    
    // Set bit at position 2
    value = value | mask;  // 00001100 (12)
    printf("After setting bit 2: %d\n", value);
    
    // Set multiple bits
    int flags = 0;    // 00000000
    flags |= 1;       // Set bit 0: 00000001
    flags |= 4;       // Set bit 2: 00000101
    flags |= 16;      // Set bit 4: 00010101
    printf("Flags with multiple bits set: %d\n", flags);
    
    return 0;
}

Bitwise XOR (^)

Basic XOR Operation

#include <stdio.h>
int main() {
    int a = 12;  // 1100 in binary
    int b = 10;  // 1010 in binary
    int result = a ^ b;  // 0110 in binary (6 in decimal)
    
    printf("a = %d (binary: 1100)\n", a);
    printf("b = %d (binary: 1010)\n", b);
    printf("a ^ b = %d (binary: 0110)\n", result);
    
    return 0;
}

XOR Properties and Applications

#include <stdio.h>
int main() {
    // XOR with itself gives 0
    int x = 25;
    printf("%d ^ %d = %d\n", x, x, x ^ x);
    
    // XOR with 0 gives original number
    printf("%d ^ 0 = %d\n", x, x ^ 0);
    
    // Toggle specific bits
    int value = 15;   // 00001111
    int toggle = 5;   // 00000101
    printf("Original: %d\n", value);
    value ^= toggle;  // Toggle bits: 00001010 (10)
    printf("After toggle: %d\n", value);
    value ^= toggle;  // Toggle again: 00001111 (15)
    printf("After second toggle: %d\n", value);
    
    return 0;
}

Simple Encryption with XOR

#include <stdio.h>
int main() {
    char message[] = "HELLO";
    int key = 123;  // Simple XOR key
    
    printf("Original message: %s\n", message);
    
    // Encrypt
    for (int i = 0; message[i] != '\0'; i++) {
        message[i] ^= key;
    }
    printf("Encrypted: ");
    for (int i = 0; message[i] != '\0'; i++) {
        printf("%c", message[i]);
    }
    printf("\n");
    
    // Decrypt (XOR again with same key)
    for (int i = 0; message[i] != '\0'; i++) {
        message[i] ^= key;
    }
    printf("Decrypted: %s\n", message);
    
    return 0;
}

Bitwise NOT (~)

Basic NOT Operation

#include <stdio.h>
int main() {
    unsigned char a = 5;  // 00000101 in binary
    unsigned char result = ~a;  // 11111010 in binary (250 in decimal)
    
    printf("a = %d (binary: 00000101)\n", a);
    printf("~a = %d (binary: 11111010)\n", result);
    
    // With integers (32-bit)
    int b = 10;
    printf("b = %d\n", b);
    printf("~b = %d\n", ~b);  // Two's complement representation
    
    return 0;
}

Left Shift (<<)

Basic Left Shift

#include <stdio.h>
int main() {
    int value = 5;  // 00000101 in binary
    
    printf("Original: %d\n", value);
    printf("Left shift by 1: %d\n", value << 1);  // 00001010 (10)
    printf("Left shift by 2: %d\n", value << 2);  // 00010100 (20)
    printf("Left shift by 3: %d\n", value << 3);  // 00101000 (40)
    
    // Left shift is equivalent to multiplication by 2^n
    printf("\nUsing left shift for multiplication:\n");
    printf("%d * 2 = %d\n", value, value << 1);
    printf("%d * 4 = %d\n", value, value << 2);
    printf("%d * 8 = %d\n", value, value << 3);
    
    return 0;
}

Right Shift (>>)

Basic Right Shift

#include <stdio.h>
int main() {
    int value = 40;  // 00101000 in binary
    
    printf("Original: %d\n", value);
    printf("Right shift by 1: %d\n", value >> 1);  // 00010100 (20)
    printf("Right shift by 2: %d\n", value >> 2);  // 00001010 (10)
    printf("Right shift by 3: %d\n", value >> 3);  // 00000101 (5)
    
    // Right shift is equivalent to division by 2^n
    printf("\nUsing right shift for division:\n");
    printf("%d / 2 = %d\n", value, value >> 1);
    printf("%d / 4 = %d\n", value, value >> 2);
    printf("%d / 8 = %d\n", value, value >> 3);
    
    return 0;
}

Practical Applications

Bit Manipulation Functions

#include <stdio.h>

// Set a specific bit
int setBit(int num, int position) {
    return num | (1 << position);
}

// Clear a specific bit
int clearBit(int num, int position) {
    return num & ~(1 << position);
}

// Toggle a specific bit
int toggleBit(int num, int position) {
    return num ^ (1 << position);
}

// Check if a specific bit is set
int checkBit(int num, int position) {
    return (num >> position) & 1;
}

int main() {
    int number = 10;  // 00001010
    
    printf("Original number: %d\n", number);
    
    // Set bit 0
    number = setBit(number, 0);
    printf("After setting bit 0: %d\n", number);  // 11
    
    // Clear bit 1
    number = clearBit(number, 1);
    printf("After clearing bit 1: %d\n", number);  // 9
    
    // Toggle bit 2
    number = toggleBit(number, 2);
    printf("After toggling bit 2: %d\n", number);  // 13
    
    // Check if bit 3 is set
    if (checkBit(number, 3)) {
        printf("Bit 3 is set\n");
    } else {
        printf("Bit 3 is not set\n");
    }
    
    return 0;
}

Counting Set Bits

#include <stdio.h>

int countSetBits(int n) {
    int count = 0;
    while (n) {
        count += n & 1;
        n >>= 1;
    }
    return count;
}

// Brian Kernighan's algorithm (more efficient)
int countSetBitsOptimized(int n) {
    int count = 0;
    while (n) {
        n &= (n - 1);  // Clear the lowest set bit
        count++;
    }
    return count;
}

int main() {
    int number = 15;  // 00001111
    
    printf("Number: %d\n", number);
    printf("Set bits (method 1): %d\n", countSetBits(number));
    printf("Set bits (optimized): %d\n", countSetBitsOptimized(number));
    
    return 0;
}

Power of Two Check

#include <stdio.h>

int isPowerOfTwo(int n) {
    return n > 0 && (n & (n - 1)) == 0;
}

int main() {
    int numbers[] = {1, 2, 3, 4, 8, 15, 16, 32, 33};
    int size = sizeof(numbers) / sizeof(numbers[0]);
    
    for (int i = 0; i < size; i++) {
        if (isPowerOfTwo(numbers[i])) {
            printf("%d is a power of 2\n", numbers[i]);
        } else {
            printf("%d is not a power of 2\n", numbers[i]);
        }
    }
    
    return 0;
}

Common Mistakes and Solutions

Operator Precedence Issues

// Wrong - bitwise operators have different precedence than arithmetic
if (x & mask == 0) {  // Evaluated as: x & (mask == 0)
    // Wrong logic
}

// Correct - use parentheses
if ((x & mask) == 0) {  // Evaluated as: (x & mask) == 0
    // Correct logic
}

Signed vs Unsigned Right Shift

// Be careful with signed numbers and right shift
int negative = -8;
printf("Signed right shift: %d\n", negative >> 1);  // May not divide by 2

// Use unsigned for predictable behavior
unsigned int positive = 8;
printf("Unsigned right shift: %u\n", positive >> 1);  // Always divides by 2

Important Points

  • Bit Positions: Numbered from 0 (rightmost) to n-1 (leftmost)
  • Performance: Bitwise operations are among the fastest CPU operations
  • Portability: Results may vary on different architectures for signed numbers
  • Overflow: Left shifting can cause overflow; right shifting can lose precision
  • Use Cases: Flags, permissions, optimization, embedded programming
  • Precedence: Bitwise operators have lower precedence than arithmetic operators

Summary

Bitwise operators provide powerful tools for direct bit manipulation in C programming. The AND (&), OR (|), XOR (^), and NOT (~) operators work on individual bits, while left shift (<<) and right shift (>>) operators move bits positions. These operators are essential for system programming, embedded development, cryptography, and performance optimization. Common applications include setting/clearing flags, checking specific bits, fast multiplication/division by powers of 2, and implementing efficient algorithms. Understanding binary representation and operator precedence is crucial for correct usage. While bitwise operations offer significant performance benefits, they require careful consideration of signed vs unsigned behavior and potential portability issues across different architectures.


Part of BCA Programming with C Course (UGCOA22J201)