Bitwise Operations
Bitwise operations are like controlling a row of light switches — each light corresponds to one bit, and you can turn on, turn off, or flip any individual light without affecting the others.
Binary Review
Computers represent all data internally using binary. One byte = 8 bits, each bit is 0 or 1.
Decimal 42 = Binary 00101010
Decimal 255 = Binary 11111111
Signed integers use two's complement representation: the highest bit is the sign bit, 0 for positive, 1 for negative.
Decimal 5 = 00000101
Decimal -5 = 11111011 (two's complement)
Six Bitwise Operators
| Operator | Name | Rule |
|---|---|---|
& |
Bitwise AND | Result is 1 only when both bits are 1 |
| |
Bitwise OR | Result is 1 when at least one bit is 1 |
^ |
Bitwise XOR | Result is 1 when the two bits differ |
~ |
Bitwise NOT | 0 becomes 1, 1 becomes 0 |
<< |
Left shift | Shifts bits left, fills low bits with 0 |
>> |
Right shift | Shifts bits right, fills high bits with sign bit (signed) or 0 (unsigned) |
Bitwise AND (&)
1010 (10)
& 1100 (12)
------
1000 (8)
Uses: clearing certain bits, checking whether certain bits are 1.
Bitwise OR (|)
1010 (10)
| 1100 (12)
------
1110 (14)
Uses: setting certain bits to 1.
Bitwise XOR (^)
1010 (10)
^ 1100 (12)
------
0110 (6)
Properties: a ^ a = 0, a ^ 0 = a. XOR is reversible — XORing twice with the same value restores the original.
Bitwise NOT (~)
~ 00101010 (42)
= 11010101 (-43, two's complement)
For unsigned numbers: ~0 = 255 (8-bit), ~0 = 0xFFFFFFFF (32-bit).
Left Shift (<<)
5 << 2
= 00000101 << 2
= 00010100
= 20
Left shifting by n is equivalent to multiplying by 2^n. 5 << 2 = 5 * 4 = 20.
Right Shift (>>)
20 >> 2
= 00010100 >> 2
= 00000101
= 5
Right shifting by n is equivalent to dividing by 2^n (rounded toward zero).
Four Essential Bit Operations
Set a Bit (to 1)
flags |= (1 << n);
Sets the nth bit to 1, leaving all other bits unchanged.
Clear a Bit (to 0)
flags &= ~(1 << n);
~(1 << n) produces a mask that is all 1s except for a 0 at the nth position. ANDing clears only that bit.
Toggle a Bit
flags ^= (1 << n);
XOR operation: 0^1=1, 1^1=0, which perfectly implements toggling.
Check a Bit
if (flags & (1 << n)) {
}
If the nth bit is 1, the result is non-zero; if 0, the result is zero.
Example
#include <stdio.h>
void print_bits(unsigned char val) {
for (int i = 7; i >= 0; i--) {
printf("%d", (val >> i) & 1);
}
printf("\n");
}
int main(void) {
unsigned char flags = 0;
flags |= (1 << 3);
printf("Set bit 3: ");
print_bits(flags);
flags |= (1 << 5);
printf("Set bit 5: ");
print_bits(flags);
flags &= ~(1 << 3);
printf("Clear bit 3: ");
print_bits(flags);
flags ^= (1 << 5);
printf("Toggle bit 5: ");
print_bits(flags);
flags ^= (1 << 7);
printf("Toggle bit 7: ");
print_bits(flags);
if (flags & (1 << 7)) {
printf("Bit 7 is 1\n");
}
return 0;
}
Set bit 3: 00001000
Set bit 5: 00101000
Clear bit 3: 00100000
Toggle bit 5: 00000000
Toggle bit 7: 10000000
Bit 7 is 1
Mask Techniques
A mask is a predefined combination of bits used to extract or modify specific bit fields within data.
Extract Low Bits
unsigned int val = 0xABCD;
unsigned int low_byte = val & 0xFF;
0xFF is a mask that keeps only the lowest 8 bits.
Extract High Bits
unsigned int high_byte = (val >> 8) & 0xFF;
First shift right by 8 bits, then mask to get the low 8 bits.
Combine Values
unsigned int combined = (high << 8) | low;
Join two bytes into a 16-bit value.
Example
RGB color value extraction. A 24-bit color value has 8 bits each for red, green, and blue:
#include <stdio.h>
int main(void) {
unsigned int color = 0xFF6633;
unsigned char r = (color >> 16) & 0xFF;
unsigned char g = (color >> 8) & 0xFF;
unsigned char b = color & 0xFF;
printf("Color #FF6633:\n");
printf(" Red: %d\n", r);
printf(" Green: %d\n", g);
printf(" Blue: %d\n", b);
unsigned int new_color = 0x00;
new_color |= ((r / 2) << 16);
new_color |= ((g / 2) << 8);
new_color |= (b / 2);
printf("Darkened: #%06X\n", new_color);
return 0;
}
Color #FF6633:
Red: 255
Green: 102
Blue: 51
Darkened: #7F3319
Permission Flags
Linux file permissions are a classic application of bitwise operations. 9 permission bits represent owner/group/other for read/write/execute:
rwxr-xr-x = 111101101 = 0755
rw-r--r-- = 110100100 = 0644
#include <stdio.h>
#define READ (1 << 2)
#define WRITE (1 << 1)
#define EXECUTE (1 << 0)
void show_permission(unsigned char perm) {
printf("%c", (perm & READ) ? 'r' : '-');
printf("%c", (perm & WRITE) ? 'w' : '-');
printf("%c", (perm & EXECUTE) ? 'x' : '-');
}
int main(void) {
unsigned char owner = READ | WRITE | EXECUTE;
unsigned char group = READ | EXECUTE;
unsigned char other = READ | EXECUTE;
printf("Permissions: ");
show_permission(owner);
show_permission(group);
show_permission(other);
printf("\n");
owner &= ~WRITE;
printf("After removing write: ");
show_permission(owner);
show_permission(group);
show_permission(other);
printf("\n");
return 0;
}
Permissions: rwxr-xr-x
After removing write: r-xr-xr-x
Practical Bit Tricks
Swap Two Variables (Without a Temporary)
a ^= b;
b ^= a;
a ^= b;
Principle: the self-inverse property of XOR. However, this hurts readability and is not recommended in real development.
Check Odd or Even
if (n & 1) {
}
If the lowest bit is 1, the number is odd. This is faster than n % 2.
Multiply/Divide by Powers of 2
n << 1
n << 2
n >> 1
Compilers typically optimize n * 2 into a shift, but shifts are only safe for positive integers.
Compute Powers of 2
unsigned int pow2 = 1u << n;
1u << 0 = 1, 1u << 1 = 2, 1u << 8 = 256... Shifting is much faster than pow(2, n).
Align to a Power of 2
unsigned int aligned = (value + mask) & ~mask;
For example, align to a 4-byte boundary: (n + 3) & ~3.
#include <stdio.h>
int main(void) {
int values[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
for (int i = 0; i < 10; i++) {
int aligned = (values[i] + 3) & ~3;
printf("%d -> %d\n", values[i], aligned);
}
return 0;
}
0 -> 0
1 -> 4
2 -> 4
3 -> 4
4 -> 4
5 -> 8
6 -> 8
7 -> 8
8 -> 8
9 -> 12
❓ FAQ
Q: Can bitwise operations be used on floating-point numbers?
memcpy.📖 Summary
- Six bitwise operators:
&,|,^,~,<<,>> - Set a bit with
|=, clear with&= ~, toggle with^=, check with& - Masks are the core technique of bitwise operations, used to extract and combine bit fields
- Permission flags are combined with OR, checked with AND, and cleared with NOT
- Left shift multiplies by 2, right shift divides by 2, align with
(n + mask) & ~mask
📝 Exercises
- Write a function that returns the number of 1-bits in an integer's binary representation (using bitwise operations, not dividing by 2 in a loop)
- Write a program that uses bitwise operations to adjust the brightness of an RGB color (multiply each channel by a coefficient and recombine)
- Write a function that extracts bits m through n from a 32-bit integer (m < n, counting from bit 0)



