0% found this document useful (0 votes)
3 views28 pages

Bitwise Complete Guide

The document is a comprehensive guide on bitwise operators, covering their concepts, applications, and algorithms. It includes detailed explanations of operators like AND, OR, XOR, NOT, left shift, and right shift, along with practical examples and use cases in embedded systems. Additionally, it provides tricks and interview problems related to bitwise operations, making it a valuable resource for understanding and utilizing these operators effectively.

Uploaded by

apradeephere43
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views28 pages

Bitwise Complete Guide

The document is a comprehensive guide on bitwise operators, covering their concepts, applications, and algorithms. It includes detailed explanations of operators like AND, OR, XOR, NOT, left shift, and right shift, along with practical examples and use cases in embedded systems. Additionally, it provides tricks and interview problems related to bitwise operations, making it a valuable resource for understanding and utilizing these operators effectively.

Uploaded by

apradeephere43
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

BITWISE OPERATORS

Complete Guide — Concepts · Tricks · Algorithms · Embedded Use

AND · OR · XOR · NOT · LEFT SHIFT · RIGHT SHIFT

Every operator explained from scratch · Truth tables · Real code patterns · Interview tricks · Algorithm uses
· Embedded hardware applications

Bitwise Operators — Complete Guide | Page 1


TABLE OF CONTENTS
Chapter 1 — Why Bitwise? — How Computers Store Numbers
• Binary, Hex, Decimal conversion
• Why bits matter in embedded
Chapter 2 — AND Operator (&)
• Truth table · bit-level behaviour
• Masking, isolating bits, clearing bits
• Checking flags · even/odd test · alignment check
• Algorithm uses: subset check, intersection
Chapter 3 — OR Operator (|)
• Truth table · bit-level behaviour
• Setting bits, combining flags
• Packing multiple values
• Algorithm uses: union of sets
Chapter 4 — XOR Operator (^)
• Truth table · bit-level behaviour
• Toggle bits · swap without temp variable
• Find non-duplicate · detect difference
• Algorithm uses: XOR linked list, XOR encryption, parity
• Missing number, single number problems
Chapter 5 — NOT Operator (~)
• Bitwise complement
• Creating masks for clearing
• One's complement, two's complement connection
Chapter 6 — Left Shift (<<)
• How it works · multiplication by powers of 2
• Setting a specific bit
• Packing data into registers
• Overflow and Undefined Behaviour rules
Chapter 7 — Right Shift (>>)
• Logical vs arithmetic shift
• Division by powers of 2
• Extracting fields from registers
• Signed vs unsigned behaviour
Chapter 8 — Compound Operators & Patterns
• Operator combinations
• Read-Modify-Write pattern
• Setting, clearing, toggling, checking bits
• Bit field extraction and insertion
Chapter 9 — Tricks & Interview Problems
• 30+ bitwise tricks with explanation
• Count set bits (Brian Kernighan)
• Power of 2 check · Isolate lowest set bit
• Reverse bits · rotate bits · absolute value
• Sign detection, min/max without branch
Chapter 10 — Algorithms Using Bitwise Operations
• XOR for finding missing/duplicate numbers
• Bit manipulation in sorting
• Subset generation · Sieve of Eratosthenes

Bitwise Operators — Complete Guide | Page 2


• Bloom filters concept · Bitmasked DP
Chapter 11 — Embedded Hardware Patterns
• GPIO control · Register RMW
• Bit-banding · Peripheral flags
• CRC, parity · Packet encoding/decoding
• Protocol bit packing (CAN, SPI, I2C frames)

Bitwise Operators — Complete Guide | Page 3


Chapter 1 — Why Bitwise? How Computers Store Numbers
Before learning the operators, understand what you are operating on. Every variable in C is stored as binary bits
in memory. Bitwise operators let you manipulate individual bits directly — something no other operator can do.

1.1 Binary, Decimal, Hex — Quick Reference


Decimal Binary (8-bit) Hex Notes

0 0000 0000 0x00 All bits off

1 0000 0001 0x01 Bit 0 set

2 0000 0010 0x02 Bit 1 set

4 0000 0100 0x04 Bit 2 set

8 0000 1000 0x08 Bit 3 set

15 0000 1111 0x0F Lower nibble all 1s

16 0001 0000 0x10 Bit 4 set

127 0111 1111 0x7F Highest positive int8

128 1000 0000 0x80 Bit 7 set (sign bit)

255 1111 1111 0xFF All 8 bits set

Note: Hex is used in embedded because each hex digit = exactly 4 bits. 0xFF = 1111 1111. Reading a
register like 0x4000 1004 is far clearer than the decimal equivalent.

1.2 Bit Numbering Convention


// Bits are numbered from 0 (rightmost = LSB) to N-1 (leftmost = MSB)
// uint8_t example:
// Bit: 7 6 5 4 3 2 1 0
// Val: 0 1 0 1 1 0 0 1 = 0x59 = 89
// | |
// MSB LSB
// uint32_t register bits:
// Bit 31 ... Bit 16 ... Bit 8 ... Bit 0
// Each bit is accessed by: (1U << bit_number)
// Bit 0 = (1U << 0) = 0x00000001
// Bit 7 = (1U << 7) = 0x00000080
// Bit 31 = (1U << 31) = 0x80000000

Bitwise Operators — Complete Guide | Page 4


Chapter 2 — AND Operator ( & )

2.1 How AND Works — Truth Table


AND outputs 1 only when BOTH input bits are 1. Think of it as a gate: output is on only when both switches are
on.

Bit A Bit B A&B Memory Aid

0 0 0 Both off → off

0 1 0 One off → off

1 0 0 One off → off

1 1 1 Both on → on

// 8-bit example
1010 1100 (0xAC = 172)
& 0000 1111 (0x0F — mask: lower nibble)
= 0000 1100 (0x0C = 12) <- only lower 4 bits kept
uint8_t a = 0xAC;
uint8_t result = a & 0x0F; // result = 0x0C

2.2 What AND is Used For


USE 1: Masking — Keep only the bits you want
Masking means keeping some bits and forcing others to 0. The mask has 1s where you want to keep, 0s where
you want to clear.
uint32_t status = 0xABCD1234;
// Extract lower byte (bits 7:0)
uint8_t low_byte = status & 0xFF; // 0x34
// Extract bits [11:8] (a 4-bit field)
uint8_t nibble = (status >> 8) & 0x0F; // 0x2
// Extract lower 12 bits (ADC result from a 32-bit register)
uint16_t adc_val = status & 0x0FFF; // 0x234
// Practical: read ADC from STM32 register
uint16_t adc_result = ADC1->DR & 0x0FFF;

USE 2: Checking if a specific bit is SET


uint32_t reg = GPIOA->IDR;
// Check if bit 5 is set (PA5 is HIGH)
if (reg & (1U << 5)) {
// bit 5 is 1
}
// Check a status flag in UART
if (USART1->SR & USART_SR_RXNE) { // RXNE = bit 5 = 0x20
uint8_t byte = USART1->DR; // data ready — read it
}
// Check multiple flags at once
if (status & (FLAG_A | FLAG_B)) {
// at least one of FLAG_A or FLAG_B is set
}
// Check ALL flags are set
if ((status & (FLAG_A | FLAG_B)) == (FLAG_A | FLAG_B)) {
// both flags are set
}

Bitwise Operators — Complete Guide | Page 5


USE 3: Clearing specific bits (using AND with inverted mask)
uint32_t reg = 0xFF;
// Clear bit 3
reg &= ~(1U << 3); // ~(0x08) = 0xFFFFFFF7
// Before: 1111 1111
// Mask : 1111 0111 (~(1<<3))
// After : 1111 0111 = 0xF7
// Clear bits [7:4]
reg &= ~(0xF0); // keep lower nibble, clear upper
// Clear GPIO mode bits [13:12] before setting new mode
GPIOA->MODER &= ~(0x3U << (5 * 2)); // clear 2 bits for pin 5

USE 4: Even / Odd Check


// A number is even if bit 0 is 0
// A number is odd if bit 0 is 1
int n = 57;
if (n & 1) {
printf("Odd");
} else {
printf("Even");
}
// This is faster than: if (n % 2 != 0)
// Bitwise AND is a single CPU instruction; modulo involves division

USE 5: Alignment Check


// Check if address is 4-byte aligned (bits 1:0 must be 00)
uint32_t addr = (uint32_t)ptr;
if (addr & 0x3) {
// NOT 4-byte aligned — may cause fault on strict-align CPU
}
// Works because 4-byte alignment means address is multiple of 4
// multiples of 4 in binary always end in 00
// Check 8-byte alignment
if (addr & 0x7) { /* not 8-byte aligned */ }
// Check power-of-2 alignment in general
// Is x aligned to N (where N is power of 2)?
if ((x & (N - 1)) == 0) { /* aligned */ }

USE 6: Extract a Subset / Intersection (Algorithm Use)


// Check if set A is a subset of set B (bitmask representation)
// Each bit represents membership in a set
uint32_t setA = 0b00001010; // elements {1, 3}
uint32_t setB = 0b00101110; // elements {1, 2, 3, 5}
// A is subset of B if (A & B) == A
if ((setA & setB) == setA) {
// A is a subset of B — all elements of A exist in B
}
// Intersection: elements in both A and B
uint32_t intersection = setA & setB; // 0b00001010 = {1,3}

TRICK: AND with (n-1) — Remove Lowest Set Bit

// n & (n-1) clears the lowest set bit of n

int n = 0b10110100;

n & (n-1) == 0b10110000 // bit 2 (value 4) was cleared

// APPLICATION: Count set bits (Brian Kernighan algorithm)

Bitwise Operators — Complete Guide | Page 6


int count_bits(uint32_t n) {

int count = 0;

while (n) {

n &= (n - 1); // remove lowest set bit each iteration

count++;

return count;

// Runs in O(number of set bits) — faster than checking all 32 bits

Interview Q: How do you check if exactly one bit is set in an integer?

Answer: Use n > 0 && (n & (n-1)) == 0. If n has exactly one set bit, n-1 will have all lower bits set and that bit
cleared, so n AND (n-1) = 0. This is also the standard way to check if a number is a power of 2.

Bitwise Operators — Complete Guide | Page 7


Chapter 3 — OR Operator ( | )

3.1 How OR Works — Truth Table


OR outputs 1 when AT LEAST ONE input bit is 1. Think of it as: any switch on → output on.

Bit A Bit B A|B Memory Aid

0 0 0 Both off → off

0 1 1 Any on → on

1 0 1 Any on → on

1 1 1 Both on → on

// 8-bit example
1010 0100 (0xA4)
| 0000 1011 (0x0B)
= 1010 1111 (0xAF) <- bits are 'added', never cleared

3.2 What OR is Used For


USE 1: Setting specific bits
uint32_t reg = GPIOA->MODER;
// Set bit 5
reg |= (1U << 5);
// Set multiple bits
reg |= (1U<<3) | (1U<<5) | (1U<<7);
// Configure GPIO pin 5 as output (bits [11:10] = 01)
GPIOA->MODER |= (1U << (5*2)); // set bit 10
// Enable multiple peripherals in RCC register
RCC->APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPAEN;

USE 2: Combining / Packing flags


// Define individual flags as single-bit values
#define FLAG_READ 0x01 // bit 0
#define FLAG_WRITE 0x02 // bit 1
#define FLAG_EXECUTE 0x04 // bit 2
#define FLAG_HIDDEN 0x08 // bit 3
// Combine flags with OR
uint8_t permissions = FLAG_READ | FLAG_WRITE; // 0x03
// Register settings
#define UART_CR1_UE (1U<<13) // UART enable
#define UART_CR1_TE (1U<<3) // Transmit enable
#define UART_CR1_RE (1U<<2) // Receive enable
USART1->CR1 |= UART_CR1_UE | UART_CR1_TE | UART_CR1_RE;

USE 3: Setting a bit-field to a value (after clearing)


// Pattern: Read-Modify-Write using AND (clear) then OR (set)
// Set bits [13:12] to 0b10 (alternate function mode for GPIO)
#define PIN 5
#define MODE 0x2U // 10 binary
// Step 1: clear the field
GPIOA->MODER &= ~(0x3U << (PIN * 2));
// Step 2: set new value
GPIOA->MODER |= (MODE << (PIN * 2));
// Combined as one-liner

Bitwise Operators — Complete Guide | Page 8


GPIOA->MODER = (GPIOA->MODER & ~(0x3U << (PIN*2))) | (MODE << (PIN*2));

USE 4: Set Union (Algorithm Use)


// OR = union of two sets represented as bitmasks
uint32_t setA = 0b00001010; // {1, 3}
uint32_t setB = 0b00100110; // {1, 2, 5}
uint32_t union_set = setA | setB; // 0b00101110 = {1, 2, 3, 5}

USE 5: Convert lowercase to uppercase and vice versa (ASCII trick)


// ASCII trick: lowercase letters have bit 5 set; uppercase don't
// 'a' = 0x61 = 0110 0001
// 'A' = 0x41 = 0100 0001
// Difference is exactly bit 5 (0x20)
char c = 'a';
char upper = c & ~0x20; // clear bit 5 -> uppercase ('A')
char lower = c | 0x20; // set bit 5 -> lowercase ('a')
// Note: works only for letters a-z, A-Z

TRICK: OR to set specific bit positions for lookup tables

// Build a bitmask for a set of values quickly

uint32_t mask = 0;

int positions[] = {1, 3, 5, 7};

for (int i = 0; i < 4; i++) {

mask |= (1U << positions[i]);

// mask = 0b10101010 — positions 1,3,5,7 are marked

// Use to test membership: is position p in the set?

if (mask & (1U << p)) { /* p is in set */ }

Interview Q: Can OR ever clear a bit?

Answer: No. OR can only set bits or leave them unchanged — it can never clear a bit. To clear bits use AND
with an inverted mask (reg &= ~mask). This is a fundamental property: OR turns bits ON, AND turns bits
OFF.

Bitwise Operators — Complete Guide | Page 9


Chapter 4 — XOR Operator ( ^ ) — The Most Versatile Operator
XOR (Exclusive OR) is the most powerful and interesting bitwise operator. It has unique mathematical
properties that are used in algorithms, cryptography, error detection, and hardware design.

4.1 How XOR Works — Truth Table


XOR outputs 1 when inputs are DIFFERENT. Outputs 0 when they are the SAME.

Bit A Bit B A^B Memory Aid

0 0 0 Same → 0

0 1 1 Different → 1

1 0 1 Different → 1

1 1 0 Same → 0

// 8-bit example
1010 1100 (0xAC)
^ 1111 0000 (0xF0)
= 0101 1100 (0x5C) <- bits flip where mask is 1
// Key XOR properties:
// a ^ a = 0 (anything XOR itself = 0)
// a ^ 0 = a (XOR with 0 = unchanged)
// a ^ b ^ b = a (XOR is its own inverse)
// XOR is commutative and associative

4.2 What XOR is Used For


USE 1: Toggle specific bits
uint32_t reg = GPIOA->ODR;
// Toggle bit 5 (flip LED state)
reg ^= (1U << 5);
// Toggle multiple bits
reg ^= (1U<<3) | (1U<<5);
// Toggle LED in a loop
while (1) {
GPIOA->ODR ^= (1U << 5); // flip PA5
delay_ms(500);
}

USE 2: Swap Two Variables WITHOUT a Temporary Variable


// Classic XOR swap — no extra memory needed
int a = 5, b = 9;
a = a ^ b; // a = 5^9 = 12
b = a ^ b; // b = 12^9 = 5 (original a)
a = a ^ b; // a = 12^5 = 9 (original b)
// Explanation:
// After step 1: a holds (a^b)
// After step 2: b = (a^b)^b = a^(b^b) = a^0 = a -- now b = original a
// After step 3: a = (a^b)^a = b^(a^a) = b^0 = b -- now a = original b
// WARNING: if a and b point to same variable, result is 0!
// Safe version with guard:
if (a != b) { a^=b; b^=a; a^=b; }
// In embedded: useful for swapping register values without extra RAM

USE 3: Find the Single Non-Duplicate Number (Famous Algorithm)

Bitwise Operators — Complete Guide | Page 10


// Problem: array has all numbers twice except one. Find it.
// Key: n ^ n = 0 and n ^ 0 = n
// XOR all elements — pairs cancel out, single remains
int arr[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 2, 6, 4};
// Wait — this has one extra. Let's use: {2, 3, 5, 3, 2}
int arr2[] = {2, 3, 5, 3, 2};
int result = 0;
for (int i = 0; i < 5; i++) {
result ^= arr2[i];
}
// result = 0^2^3^5^3^2 = (2^2)^(3^3)^5 = 0^0^5 = 5
printf("%d", result); // 5 — the single number
// O(n) time, O(1) space — optimal solution

USE 4: Find Missing Number in Range 1 to N


// Array has numbers 1 to N with one missing. Find it.
// XOR all array elements with all numbers 1 to N
// Duplicates cancel, missing number remains
int arr[] = {1, 2, 4, 5}; // N=5, missing=3
int n = 5;
int xor_result = 0;
for (int i = 0; i < n-1; i++) xor_result ^= arr[i]; // XOR all elements
for (int i = 1; i <= n; i++) xor_result ^= i; // XOR 1 to N
printf("%d", xor_result); // 3 — the missing number

USE 5: Parity Check — Error Detection


// XOR all bits: result is 1 if odd number of 1s (odd parity), 0 if even
uint8_t compute_parity(uint32_t n) {
// Method 1: XOR all bits
n ^= n >> 16;
n ^= n >> 8;
n ^= n >> 4;
n ^= n >> 2;
n ^= n >> 1;
return n & 1; // 1 = odd parity, 0 = even parity
}
// Used in UART parity bit, CAN frame, RS-485 data integrity

USE 6: Simple XOR Encryption / Obfuscation


// XOR cipher: apply same key to encrypt and decrypt
// because: data ^ key ^ key = data
void xor_encrypt(uint8_t *buf, uint16_t len, uint8_t key) {
for (uint16_t i = 0; i < len; i++) {
buf[i] ^= key;
} }
// Encrypt and decrypt use the EXACT same function
xor_encrypt(data, len, 0xA5); // encrypt
xor_encrypt(data, len, 0xA5); // decrypt — same operation!
// Used in: embedded bootloader checksum, simple packet obfuscation
// NOT secure for real cryptography — use AES for that

USE 7: Detect Bit Difference / Changed Bits


// XOR two values: result has 1s where bits differ
uint32_t old_state = 0b10110010;
uint32_t new_state = 0b10011110;
uint32_t changed = old_state ^ new_state; // = 0b00101100
// Bits 2,3,5 changed
// Application: detect GPIO pin state changes

Bitwise Operators — Complete Guide | Page 11


uint32_t prev = GPIOB->IDR;
// ... time passes ...
uint32_t curr = GPIOB->IDR;
uint32_t edges = prev ^ curr; // which pins changed state?
if (edges & (1U << 3)) {
// pin 3 changed — was it rising or falling?
if (curr & (1U << 3)) handle_rising_edge();
else handle_falling_edge();
}

TRICK: XOR without branch — Conditional value selection

// Select between two values without if-else

// If condition is 0: result = a. If condition is 1: result = a ^ (a^b) = b

int a = 10, b = 20;

int condition = 1; // 0 or 1 only

int result = a ^ (-(condition) & (a ^ b));

// condition=0 -> result=a, condition=1 -> result=b

// Used in: branchless code for real-time embedded, DSP, compilers

Interview Q: What does n ^ n equal and why is that useful?

Answer: n ^ n always equals 0 for any value of n. This is because XOR compares bit-by-bit: identical bits
always produce 0. This property is what makes XOR useful for finding duplicates/missing numbers. XOR all
elements — duplicates cancel to 0, and the leftover is the unique element.

Bitwise Operators — Complete Guide | Page 12


Chapter 5 — NOT Operator ( ~ ) — Bitwise Complement

5.1 How NOT Works


~ flips every single bit: 0 becomes 1, 1 becomes 0. Also called one's complement.

Bit A ~A Note

0 1 Flipped

1 0 Flipped

uint8_t a = 0b10110100; // 0xB4


uint8_t b = ~a; // 0b01001011 = 0x4B
uint8_t a = 0x0F; // 0000 1111
uint8_t b = ~a; // 1111 0000 = 0xF0
// Important: ~ promotes to int first!
uint8_t x = 0x0F;
uint32_t y = ~x; // y = 0xFFFFFFF0 (NOT 0xF0!)
// because x promoted to int before NOT
// Safe approach: mask after NOT
uint8_t mask = ~0x0F & 0xFF; // 0xF0
// OR: work with correct type from the start
uint32_t m = ~(0x0FU); // 0xFFFFFFF0
WARNING: ~ promotes the operand to int. ~(uint8_t)0x0F gives 0xFFFFFFF0, not 0xF0. Always be
aware of the type when using NOT.

5.2 Primary Use: Creating Inverted Masks for Clearing Bits


// To clear bit N: AND with NOT of (1 << N)
uint32_t reg = 0xFF;
reg &= ~(1U << 5); // clear bit 5
// ~(1U<<5) = ~0x20 = 0xFFFFFFDF
// Clear a multi-bit field (bits [7:4])
reg &= ~(0xF0U); // clear upper nibble
// Clear bits [13:12] for GPIO mode of pin 5
GPIOA->MODER &= ~(0x3U << (5*2)); // clear 2 bits at position 10
// Why not just hardcode the mask?
// ~(0x3U << 10) is CLEARER in intent than 0xFFFFF3FF
// The formula shows you are working with pin 5, 2-bit field

5.3 One's Complement and Two's Complement Connection


// ~n = one's complement of n
// -n = two's complement = ~n + 1
int n = 5; // 0000 0101
int ones = ~n; // 1111 1010 = -6
int twos = ~n + 1; // 1111 1011 = -5 (same as -n)
// Trick: ~n + 1 == -n always holds for signed integers
// Useful: negate without unary minus
int neg = ~n + 1; // = -n
// Two's complement check
// n + (~n) = -1 always (all 1s in binary = -1 in two's complement)
int check = n + (~n); // always -1

Bitwise Operators — Complete Guide | Page 13


Chapter 6 — Left Shift Operator ( << )

6.1 How Left Shift Works


Left shift moves all bits to the left by n positions. Bits shifted off the left end are lost. Zeros fill in from the right.
// Shift left by 1
0000 1010 (10)
<< 1
0001 0100 (20) <- multiplied by 2
// Shift left by 3
0000 0011 (3)
<< 3
0001 1000 (24) <- multiplied by 8 = 3 * 2^3
uint8_t a = 3;
uint8_t b = a << 3; // b = 24
uint32_t x = 1U << 0; // 0x00000001 = bit 0
uint32_t y = 1U << 7; // 0x00000080 = bit 7
uint32_t z = 1U << 31; // 0x80000000 = bit 31

6.2 Left Shift = Multiply by Powers of 2


Expression Value Equivalent multiply

x << 0 x x*1

x << 1 x*2 x*2

x << 2 x*4 x*4

x << 3 x*8 x*8

x << 4 x * 16 x * 16

x << 8 x * 256 x * 256

x << 10 x * 1024 x * 1024 (= x * 1K)

x << 20 x * 1048576 x * 1M

Shift is faster than multiplication on most MCUs that don't have a hardware multiplier (like small AVR, MSP430).
Even on Cortex-M4 with hardware MUL, the compiler uses shifts for constant power-of-2 multiplications
automatically.

6.3 Practical Uses of Left Shift


USE 1: Set a specific bit by position
// The most common embedded use
#define PIN 5
uint32_t mask = (1U << PIN); // bit 5 = 0x20
GPIOA->ODR |= (1U << PIN); // set pin 5 high
GPIOA->ODR &= ~(1U << PIN); // set pin 5 low
GPIOA->ODR ^= (1U << PIN); // toggle pin 5
// Why not just write the constant 0x20?
// (1U << 5) is self-documenting — clearly says 'bit 5'
// (1U << PIN) works for any pin number variable
USE 2: Multiply by power of 2 — fast arithmetic
// Compute array index for a 2D array with width 64
// row * 64 + col == (row << 6) + col

Bitwise Operators — Complete Guide | Page 14


uint32_t pixel = (row << 6) + col; // faster than row*64
// Convert kB to bytes
uint32_t bytes = 4 << 10; // 4 * 1024 = 4096
// Hash function (fast bit mixing)
hash = (hash << 5) + hash + c; // hash * 33 + c

USE 3: Pack multiple values into one variable


// Pack 4 bytes into a uint32_t (e.g., IP address, color)
uint8_t r=255, g=128, b=64, a=255;
uint32_t color = ((uint32_t)a << 24) |
((uint32_t)r << 16) |
((uint32_t)g << 8) |
((uint32_t)b << 0);
// color = 0xFF FF80 40
// Pack CAN message ID
uint32_t can_id = ((uint32_t)priority << 26) |
((uint32_t)pgn << 8) |
((uint32_t)source << 0);

6.4 Left Shift Rules & Undefined Behaviour


• Shifting by a negative amount: Undefined Behaviour.
• Shifting by >= bit-width of the type: Undefined Behaviour (e.g., 1 << 32 on a 32-bit int).
• Left-shifting a negative signed value: Undefined Behaviour.
• Left-shifting a positive signed value into the sign bit: Undefined Behaviour.
• SAFE RULE: Always use unsigned types and shift amount < bit-width.
// WRONG
int mask = 1 << 31; // UB: shifts positive value to sign bit
int bad = 1 << 32; // UB: shift by >= bit width
// CORRECT
uint32_t mask = 1U << 31; // OK: unsigned
uint64_t big = 1ULL << 32; // OK: 64-bit type
// Safe general pattern
uint32_t set_bit(uint8_t n) {
if (n >= 32) return 0;
return 1U << n;
}

Bitwise Operators — Complete Guide | Page 15


Chapter 7 — Right Shift Operator ( >> )

7.1 Two Types of Right Shift


Type Applied to Fill from left Behaviour

Logical shift Unsigned types 0 (zero fill) Always fills 0s — safe, predictable

Preserves sign — implementation


Arithmetic shift Signed types Sign bit (0 or 1) defined in C

// UNSIGNED right shift — always logical (zero fill)


uint8_t a = 0b10110100; // 180
a >> 1; // 0b01011010 = 90 <- 0 filled from left
// SIGNED right shift — implementation defined (usually arithmetic on ARM)
int8_t b = 0b10110100; // -76 in two's complement
b >> 1; // 0b11011010 = -38 <- sign bit filled (ARM)
// or could be 0b01011010 = 90 (logical, standard says either)
// RULE: For predictable code, always right-shift UNSIGNED types only
// For signed, use explicit cast or divide
WARNING: Right shifting a signed negative value is implementation-defined in C. If you need
arithmetic right shift, cast to unsigned or use division for portability.

7.2 Right Shift = Divide by Powers of 2


Expression Value Equivalent divide

x >> 1 x/2 (floor) floor(x/2)

x >> 2 x/4 floor(x/4)

x >> 3 x/8 floor(x/8)

x >> 8 x/256 floor(x/256)

x >> 10 x/1024 x / 1K

Note: Right shift divides by power of 2 with floor rounding. For signed negative numbers: -7 >> 1 may give -4
(arithmetic) not -3 (truncation toward zero). Use unsigned or explicit division when exact rounding matters.

7.3 Practical Uses of Right Shift


USE 1: Extract a specific bit field from a register
uint32_t reg = GPIOA->MODER; // 32-bit GPIO mode register
// Each pin has 2 bits. Pin 5 is at bits [11:10]
// Extract mode of pin 5
uint8_t mode = (reg >> (5 * 2)) & 0x3;
// Step 1: shift right by 10 — brings bits[11:10] to bits[1:0]
// Step 2: AND with 0x3 — keep only the 2 bits we want
// Read ADC result from bits [11:0]
uint16_t adc = (ADC1->DR >> 0) & 0xFFF; // or just ADC1->DR & 0xFFF
// Get priority field from CAN message ID bits [28:26]
uint8_t priority = (can_id >> 26) & 0x7; // 3-bit field

USE 2: Split a value into bytes (serialize for UART/SPI)


uint32_t value = 0xDEADBEEF;
uint8_t byte3 = (value >> 24) & 0xFF; // 0xDE (most significant)
uint8_t byte2 = (value >> 16) & 0xFF; // 0xAD

Bitwise Operators — Complete Guide | Page 16


uint8_t byte1 = (value >> 8) & 0xFF; // 0xBE
uint8_t byte0 = (value >> 0) & 0xFF; // 0xEF (least significant)
// Send big-endian over UART
uint8_t buf[4] = {byte3, byte2, byte1, byte0};
uart_send(buf, 4);
// Reverse: reconstruct from bytes
uint32_t reconstructed = ((uint32_t)byte3 << 24) |
((uint32_t)byte2 << 16) |
((uint32_t)byte1 << 8) |
((uint32_t)byte0 << 0);

USE 3: Fast average of two numbers (no overflow)


// Average without overflow — used in binary search
// (a + b) might overflow if both are large!
// WRONG (overflow risk)
int mid = (low + high) / 2;
// CORRECT — no overflow
int mid = low + ((high - low) >> 1);
// OR for unsigned:
uint32_t mid = (low + high) >> 1; // safe for unsigned (overflow wraps and shifts)
// Actually cleanest:
uint32_t mid = low + ((high - low) / 2);

USE 4: Iterate over all bits of a number


// Print binary representation
void print_binary(uint32_t n) {
for (int i = 31; i >= 0; i--) {
printf("%d", (n >> i) & 1); // extract bit i
if (i % 8 == 0) printf(" ");
}
printf("\n");
}
// Check each bit and perform action
for (int i = 0; i < 32; i++) {
if ((flags >> i) & 1) {
handle_flag(i);
}
}

Bitwise Operators — Complete Guide | Page 17


Chapter 8 — Compound Operators & Complete Bit Patterns

8.1 The 4 Core Bit Operations — Complete Reference


Operation Code How it works

Set bit N reg |= (1U << N) OR with 1 at position N

Clear bit N reg &= ~(1U << N) AND with 0 at position N

Toggle bit N reg ^= (1U << N) XOR with 1 at position N

Read bit N (reg >> N) & 1 Shift to position 0, mask

Check bit N reg & (1U << N) Non-zero if bit is set

8.2 Bit Field — Extract, Insert, Clear


// For a field of WIDTH bits starting at SHIFT position:
#define FIELD_SHIFT 8
#define FIELD_WIDTH 4
#define FIELD_MASK (((1U << FIELD_WIDTH) - 1) << FIELD_SHIFT)
// FIELD_MASK = 0x0F00 = bits [11:8]
// EXTRACT the field
uint32_t val = (reg & FIELD_MASK) >> FIELD_SHIFT;
// INSERT a value into the field
reg = (reg & ~FIELD_MASK) | ((newval << FIELD_SHIFT) & FIELD_MASK);
// CLEAR the field
reg &= ~FIELD_MASK;
// General macro
#define BF_MASK(shift, width) (((1U<<(width))-1) << (shift))
#define BF_GET(reg,shift,width) (((reg) >> (shift)) & ((1U<<(width))-1))
#define BF_SET(reg,val,shift,width) \
((reg) = ((reg) & ~BF_MASK(shift,width)) | (((val)<<(shift)) & BF_MASK(shift,width)))

8.3 Compound Assignment Operators


Operator Meaning Example Result

&= reg = reg & x reg &= 0x0F Keep lower nibble only

|= reg = reg | x reg |= (1U<<5) Set bit 5

^= reg = reg ^ x reg ^= (1U<<5) Toggle bit 5

<<= reg = reg << n reg <<= 2 Multiply by 4

Divide by 16 / extract upper


>>= reg = reg >> n reg >>= 4 nibble

8.4 Complete GPIO Register Control Example


// Full GPIO pin configuration example for STM32
// Configure PA5 as output push-pull, high speed
#define PIN 5
// 1. Enable GPIOA clock
RCC->AHB1ENR |= (1U << 0);
// 2. Set mode to output (MODER bits [11:10] = 01)

Bitwise Operators — Complete Guide | Page 18


GPIOA->MODER &= ~(0x3U << (PIN * 2)); // clear 2 bits
GPIOA->MODER |= (0x1U << (PIN * 2)); // set to 01 = output
// 3. Set output type to push-pull (OTYPER bit 5 = 0)
GPIOA->OTYPER &= ~(1U << PIN);
// 4. Set speed to high (OSPEEDR bits [11:10] = 10)
GPIOA->OSPEEDR &= ~(0x3U << (PIN * 2));
GPIOA->OSPEEDR |= (0x2U << (PIN * 2));
// 5. No pull-up/pull-down (PUPDR bits = 00)
GPIOA->PUPDR &= ~(0x3U << (PIN * 2));
// Now control:
GPIOA->ODR |= (1U << PIN); // HIGH
GPIOA->ODR &= ~(1U << PIN); // LOW
GPIOA->ODR ^= (1U << PIN); // TOGGLE
// Using BSRR for atomic set/reset (no read needed — safer in ISR)
GPIOA->BSRR = (1U << PIN); // atomic SET
GPIOA->BSRR = (1U << (PIN + 16)); // atomic RESET

Bitwise Operators — Complete Guide | Page 19


Chapter 9 — Tricks & Interview Problems — 30+ Patterns

9.1 Power of 2 Checks


// Is n a power of 2?
// Powers of 2: 1,2,4,8,16... in binary: 1, 10, 100, 1000
// n & (n-1) removes the lowest set bit
// If n is power of 2, it has exactly one set bit, so result = 0
bool is_power_of_2(uint32_t n) {
return (n > 0) && ((n & (n-1)) == 0);
}
is_power_of_2(8) // 1000 & 0111 = 0 -> TRUE
is_power_of_2(6) // 0110 & 0101 = 0100 -> FALSE
// Round up to next power of 2
uint32_t next_pow2(uint32_t n) {
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
return n + 1;
}
// next_pow2(5)=8, next_pow2(8)=8, next_pow2(9)=16

9.2 Count Set Bits (Hamming Weight / popcount)


// Method 1: Brian Kernighan — O(set bits)
int popcount(uint32_t n) {
int count = 0;
while (n) { n &= (n-1); count++; }
return count;
}
// Method 2: Parallel bit counting — O(log N)
uint32_t popcount_fast(uint32_t n) {
n = n - ((n >> 1) & 0x55555555);
n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
n = (n + (n >> 4)) & 0x0F0F0F0F;
return (n * 0x01010101) >> 24;
}
// Method 3: Built-in (GCC/Clang — use when available)
int count = __builtin_popcount(n); // 32-bit
int count = __builtin_popcountll(n); // 64-bit

9.3 Isolate & Manipulate Lowest Set Bit


// Isolate lowest set bit (returns single bit set at that position)
uint32_t lowest = n & (-n); // or n & (~n + 1)
// n=12=1100 -> lowest = 0100 = 4
// Remove lowest set bit
n &= (n - 1);
// Set all bits below lowest set bit to 1
n |= (n - 1);
// Turn on lowest 0 bit (find and set lowest clear bit)
n |= (n + 1);

Bitwise Operators — Complete Guide | Page 20


// Isolate a run of trailing 1s (e.g. 0b10111 -> 0b00111)
uint32_t trailing = n ^ (n+1); // then >> 1 to get mask

9.4 Reverse Bits


// Reverse all 32 bits of a number
uint32_t reverse_bits(uint32_t n) {
n = ((n >> 1) & 0x55555555) | ((n & 0x55555555) << 1);
n = ((n >> 2) & 0x33333333) | ((n & 0x33333333) << 2);
n = ((n >> 4) & 0x0F0F0F0F) | ((n & 0x0F0F0F0F) << 4);
n = ((n >> 8) & 0x00FF00FF) | ((n & 0x00FF00FF) << 8);
n = ( n >> 16) | ( n << 16);
return n;
}
// 0x80000000 -> 0x00000001
// Used in: DSP bit-reversal permutation (FFT), CRC reflection

9.5 Rotate Bits (Circular Shift)


// Rotate left by n bits (bits shifted out from left come back on right)
uint32_t rotate_left(uint32_t val, uint8_t n) {
return (val << n) | (val >> (32 - n));
}
// Rotate right by n bits
uint32_t rotate_right(uint32_t val, uint8_t n) {
return (val >> n) | (val << (32 - n));
}
// rotate_left(0b10110001, 2) = 0b11000110
// Used in: cryptographic algorithms (AES, SHA), CRC computation
// GCC built-in (very fast on ARM with ROR instruction)
uint32_t ror = __builtin_arm_ror(val, n); // ARM-specific

9.6 Sign and Absolute Value Without Branch


// Get sign of integer: +1, 0, or -1
int sign(int n) {
return (n > 0) - (n < 0);
}
// Check if negative (just test sign bit)
int is_negative = (n >> 31) & 1; // 1 if negative, 0 if positive/zero
// Absolute value without branch (two's complement trick)
int abs_val(int n) {
int mask = n >> 31; // mask = -1 (0xFFFFFFFF) if negative, 0 if positive
return (n + mask) ^ mask;
// if positive: (n+0)^0 = n
// if negative: (n-1)^(-1) = ~(n-1) = -n (two's complement negation)
}

9.7 Min / Max Without Branch


// Branchless min/max using arithmetic right shift
// Works on systems with arithmetic right shift (ARM, x86)
int branchless_min(int a, int b) {
int diff = a - b;
int mask = diff >> 31; // -1 if a<b, 0 if a>=b
return b + (diff & mask);
}
int branchless_max(int a, int b) {
int diff = a - b;

Bitwise Operators — Complete Guide | Page 21


int mask = diff >> 31;
return a - (diff & mask);
}
// Used in: DSP, image processing — avoid pipeline stalls from branches

9.8 Multiply and Divide by Non-Power-of-2 Using Shifts


// Multiply by 10 = x*8 + x*2 = (x<<3) + (x<<1)
int mul10(int x) { return (x << 3) + (x << 1); }
// Multiply by 3 = x*2 + x = (x<<1) + x
int mul3(int x) { return (x << 1) + x; }
// Multiply by 7 = x*8 - x = (x<<3) - x
int mul7(int x) { return (x << 3) - x; }
// These are used by compilers automatically when optimising constant multiplications
// Useful to know for manual DSP code on MCUs without hardware multiplier

9.9 More Interview Tricks


Trick Code Explanation

Turn off rightmost bit n & (n-1) Clears lowest set bit

Isolate rightmost bit n & (-n) Only lowest set bit

Turn on rightmost 0 bit n | (n+1) Sets lowest clear bit

Turn off trailing 1s n & (n+1) Clears trailing run of 1s

Turn on trailing 0s n | (n-1) Sets trailing run of 0s

Check if power of 2 (n>0)&&!(n&(n-1)) True for exactly 1 set bit

Swap nibbles of byte (x>>4)|(x<<4) High/low 4 bits swapped

XOR bits in pair n ^ (n>>1) Gray code conversion

Count trailing zeros __builtin_ctz(n) CPU instruction on GCC

Count leading zeros __builtin_clz(n) Find MSB position

Next number same bit


count See Gosper's Hack Useful for subset enumeration

Bitwise Operators — Complete Guide | Page 22


Chapter 10 — Algorithms Using Bitwise Operations

10.1 Two Numbers Appearing Once (others twice)


// Array has all numbers twice except TWO. Find both.
// Step 1: XOR all -> get x^y (XOR of the two unique numbers)
// Step 2: Find any set bit in x^y (they differ at this bit)
// Step 3: Divide array into two groups by that bit
// Step 4: XOR each group — single number cancels pairs
void find_two_unique(int *arr, int n, int *a, int *b) {
int xor_all = 0;
for (int i = 0; i < n; i++) xor_all ^= arr[i];
// Find rightmost set bit (bit where a and b differ)
int diff_bit = xor_all & (-xor_all);
*a = 0; *b = 0;
for (int i = 0; i < n; i++) {
if (arr[i] & diff_bit) *a ^= arr[i];
else *b ^= arr[i];
}
}

10.2 Subset Enumeration Using Bitmask


// Enumerate ALL subsets of a set of N elements
// Each bit in mask represents whether element i is in subset
void enumerate_subsets(int n) {
int total = 1 << n; // 2^n total subsets
for (int mask = 0; mask < total; mask++) {
printf("Subset: { ");
for (int i = 0; i < n; i++) {
if (mask & (1 << i))
printf("%d ", i);
}
printf("}\n");
}
}
// For n=3: generates {},{0},{1},{0,1},{2},{0,2},{1,2},{0,1,2}
// Used in: DP on subsets, graph problems, combination generation
// Time: O(2^N * N) — practical for N <= 20

10.3 Bitmask DP — Classic Travelling Salesman (TSP) idea


// dp[mask][i] = min cost to visit all cities in mask, ending at city i
// mask is a bitmask of visited cities
// This is just the concept — shows how bitmask encodes state
int dp[1<<N][N];
// Transition: visit next city j not yet in mask
for (int mask = 0; mask < (1<<N); mask++) {
for (int i = 0; i < N; i++) {
if (!(mask & (1<<i))) continue; // i must be in mask
for (int j = 0; j < N; j++) {
if (mask & (1<<j)) continue; // j must NOT be in mask
int new_mask = mask | (1<<j); // add j to visited set
dp[new_mask][j] = min(dp[new_mask][j], dp[mask][i] + dist[i][j]);
}

Bitwise Operators — Complete Guide | Page 23


}
}

10.4 Sieve of Eratosthenes with Bitmask (memory efficient)


// Standard sieve uses bool array: 1 byte per number
// Bitmask sieve: 1 BIT per number — 8x less memory
#define MAX 1000000
uint32_t sieve[MAX/32 + 1]; // 1 bit per number
#define IS_COMPOSITE(n) (sieve[(n)>>5] & (1U << ((n)&31)))
#define SET_COMPOSITE(n) (sieve[(n)>>5] |= (1U << ((n)&31)))
void build_sieve(void) {
memset(sieve, 0, sizeof(sieve));
for (int i = 2; (long long)i*i < MAX; i++) {
if (!IS_COMPOSITE(i)) {
for (int j = i*i; j < MAX; j += i)
SET_COMPOSITE(j);
}
}
}
// Memory: 1M numbers / 8 bits = 125 KB (vs 1 MB for bool array)

10.5 Gray Code — One Bit Changes at a Time


// Gray code: consecutive values differ by exactly 1 bit
// Used in: rotary encoders, error reduction in ADC/DAC
// Binary to Gray
uint32_t bin_to_gray(uint32_t n) {
return n ^ (n >> 1);
}
// Gray to Binary
uint32_t gray_to_bin(uint32_t g) {
uint32_t b = g;
while (g >>= 1) b ^= g;
return b;
}
// Binary: 0 1 2 3 4 5 6 7
// Gray: 0 1 3 2 6 7 5 4
// Changes: - 1 1 1 1 1 1 1 (exactly 1 bit each step)
// Application: rotary encoder hall sensor — on mechanical switch bounce,
// only 1-bit errors occur so position error is at most 1 step

Bitwise Operators — Complete Guide | Page 24


Chapter 11 — Embedded Hardware Patterns

11.1 Register Read-Modify-Write — The Core Pattern


NEVER write a full register unless you know and intend all bits. Other bits may control other functions. Always
read first, modify only your bits, write back.
// Template for any register field modification
REG = (REG & ~MASK) | (VALUE << SHIFT);
// Configure USART baud rate divider [15:4] = mantissa, [3:0] = fraction
USART1->BRR = (mantissa << 4) | (fraction & 0xF);
// Enable SPI: set SPE bit, keep all other bits unchanged
SPI1->CR1 |= SPI_CR1_SPE; // SPI_CR1_SPE = (1U<<6)
// Configure I2C clock: set bits [7:6] (FM duty cycle), [5:0] (CCR)
I2C1->CCR = (duty << 14) | (1U << 15) | ccr_val;

11.2 SPI Byte Packing / Unpacking


// Pack sensor data into SPI frame (16-bit: 4-bit addr + 12-bit data)
uint16_t spi_frame = ((addr & 0xF) << 12) | (data & 0xFFF);
// Unpack received SPI frame
uint8_t recv_addr = (frame >> 12) & 0xF;
uint16_t recv_data = frame & 0xFFF;
// Send 16-bit value as 2 bytes MSB first
spi_send((value >> 8) & 0xFF); // high byte first
spi_send((value >> 0) & 0xFF); // low byte
// Reconstruct 16-bit from 2 received bytes
uint16_t val = ((uint16_t)rx_high << 8) | rx_low;

11.3 CAN Message Frame — Bit Field Manipulation


// J1939 / CAN 2.0B 29-bit extended ID structure
// Bits [28:26] = Priority (3 bits)
// Bits [25:24] = Reserved/DataPage (2 bits)
// Bits [23:16] = PGN high byte (8 bits)
// Bits [15:8] = PGN low byte (8 bits)
// Bits [7:0] = Source address (8 bits)
uint32_t build_can_id(uint8_t priority, uint16_t pgn, uint8_t src) {
return ((uint32_t)(priority & 0x7) << 26) |
((uint32_t)(pgn & 0x3FFFF) << 8) |
((uint32_t)(src & 0xFF));
}
uint8_t get_priority(uint32_t id) { return (id >> 26) & 0x7; }
uint32_t get_pgn(uint32_t id) { return (id >> 8) & 0x3FFFF; }
uint8_t get_src(uint32_t id) { return (id >> 0) & 0xFF; }

11.4 CRC and Checksum Using XOR


// Simple XOR checksum (used in many embedded protocols)
uint8_t xor_checksum(uint8_t *buf, uint16_t len) {
uint8_t cs = 0;
for (uint16_t i = 0; i < len; i++) cs ^= buf[i];
return cs;
}
// Verify: XOR all data + checksum should = 0
bool verify_checksum(uint8_t *buf, uint16_t len, uint8_t expected) {
return xor_checksum(buf, len) == expected;

Bitwise Operators — Complete Guide | Page 25


}
// CRC8 with polynomial 0x07 (software table-driven)
uint8_t crc8_update(uint8_t crc, uint8_t data) {
crc ^= data;
for (int i = 0; i < 8; i++) {
if (crc & 0x80) crc = (crc << 1) ^ 0x07;
else crc = (crc << 1);
}
return crc;
}

11.5 Interrupt Flag Handling Pattern


// Read interrupt status, handle each set flag, then clear it
void USART1_IRQHandler(void) {
uint32_t sr = USART1->SR; // snapshot status register
if (sr & USART_SR_RXNE) { // receive not empty
uint8_t byte = USART1->DR & 0xFF;
rx_buffer[rx_head++] = byte;
// Reading DR automatically clears RXNE on STM32
}
if (sr & USART_SR_TXE) { // transmit empty
if (tx_head != tx_tail) {
USART1->DR = tx_buffer[tx_tail++];
} else {
USART1->CR1 &= ~USART_CR1_TXEIE; // disable TX interrupt
}
}
if (sr & USART_SR_ORE) { // overrun error — clear it
(void)USART1->SR; // read SR then DR to clear ORE
(void)USART1->DR;
}
}

11.6 Atomic Bit Operations — Critical Sections


// Problem: counter++ is 3 instructions (Read-Modify-Write)
// If ISR modifies same variable between these, value is corrupted
// Solution 1: Disable/enable interrupts around RMW
__disable_irq(); // or: uint32_t primask = __get_PRIMASK(); __disable_irq();
shared_counter++;
__enable_irq(); // or: __set_PRIMASK(primask);
// Solution 2: ARM Cortex-M LDREX/STREX (exclusive access)
// (handled by __LDREXW / __STREXW intrinsics in CMSIS)
// Solution 3: C11 atomics (if compiler supports)
#include <stdatomic.h>
atomic_uint_fast32_t counter = 0;
atomic_fetch_add(&counter, 1); // atomic increment
// ARM specific: bit-banding for atomic single-bit access
// Bit-band address formula for peripheral region:
#define PERIPH_BB(addr, bit) \
(*((volatile uint32_t *)(0x42000000 + (((addr)-0x40000000)*32) + ((bit)*4))))
// Atomic set bit 5 of GPIOA_ODR without read-modify-write
PERIPH_BB(0x40020014, 5) = 1;

Bitwise Operators — Complete Guide | Page 26


Quick Reference — Bitwise Cheat Sheet

Operator Summary
Op Name Output 1 when... Main Use

& AND Both bits are 1 Mask, clear bits, check bits, extract field

| OR Any bit is 1 Set bits, combine flags, pack values

^ XOR Bits are different Toggle, swap, find unique, parity, encrypt

~ NOT Input is 0 Invert mask for clearing bits

<< Left shift (bits move left) Set bit N, multiply by 2^N, pack bytes

>> Rght shift (bits move right) Extract field, divide by 2^N, unpack bytes

Golden Formulas
Operation Formula/Code

Set bit N reg |= (1U << N)

Clear bit N reg &= ~(1U << N)

Toggle bit N reg ^= (1U << N)

Check bit N if (reg & (1U << N))

Extract bits [hi:lo] (reg >> lo) & ((1U << (hi-lo+1)) - 1)

Insert value into field reg = (reg & ~MASK) | ((val << SHIFT) & MASK)

Is power of 2 (n > 0) && !(n & (n-1))

Remove lowest set bit n &= (n-1)

Isolate lowest set bit n & (-n)

Count set bits while(n) { n&=(n-1); count++; }

XOR swap a^=b; b^=a; a^=b;

Find single number result = XOR of all elements

Even/Odd check n & 1 (1=odd, 0=even)

Sign of integer (n >> 31) & 1 (1=negative)

Rotate left N (val << N) | (val >> (32-N))

Rotate right N (val >> N) | (val << (32-N))

Pack 4 bytes (a<<24)|(b<<16)|(c<<8)|d

Byte N of value (val >> (N*8)) & 0xFF

When to Use Which Operator — Decision Guide


You want to... Use this Example

Keep specific bits, zero rest & mask x & 0x0F

Bitwise Operators — Complete Guide | Page 27


Turn specific bits ON | mask reg |= (1<<5)

Flip specific bits ^ mask reg ^= (1<<5)

Turn specific bits OFF & ~mask reg &= ~(1<<5)

Test if bit is set & (1U<<N) if (x & (1<<3))

Multiply by power of 2 << n x << 3 = x*8

Divide by power of 2
(unsigned) >> n x >> 2 = x/4

Move bits to position 0


(extract) >> shift reg >> 8

Move value to correct field


position << shift val << 8

Invert all bits ~ ~0x0F = 0xF0

Detect changed bits old ^ new diff = a ^ b

Cancel duplicate values XOR all elements find unique number

Check both conditions at once & both flags reg & (A|B)

END OF GUIDE
This guide is part of the C Programming for Embedded & ECE Engineers series. Phase 2 covers: Advanced
Pointers, Memory Management, RTOS in C, ISR Design, Communication Protocols, MISRA C, Debugging, and
Company Interview Problems.

Bitwise Operators — Complete Guide | Page 28

You might also like