How computers actually think. Every image, video, PDF, and line of code you have ever seen is just a massive pile of 0s and 1s. This page teaches you to see the world the way a computer does -- and to manipulate data at the lowest level.
Understanding binary is not optional for serious programmers. Bit manipulation shows up in technical interviews, systems programming, networking, cryptography, graphics, and game dev. If you want to write C++ at a high level, you need to think in binary fluently.
You count in base 10 every day. That means you use 10 digits: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. When you run out of digits, you carry over to the next place. After 9 comes 10 -- you put a 1 in the "tens" column and start over.
Binary is base 2. You only have 2 digits: 0 and 1. That is it. When you run out (after 1), you carry over. After 1 comes 10 (which means "2" in binary).
Why only 0 and 1? Because computers are built from billions of tiny switches (transistors) that can only be in two states: off (0) or on (1). Binary is not a choice -- it is a physical limitation. Everything a computer does comes down to flipping these switches.
Counting in binary works exactly like counting in decimal, just with fewer digits. Watch the pattern:
Look at the rightmost column: it alternates 0, 1, 0, 1, 0, 1... (every number).
The second column: 0, 0, 1, 1, 0, 0, 1, 1... (every 2 numbers).
The third column: 0, 0, 0, 0, 1, 1, 1, 1... (every 4 numbers).
Each column doubles the frequency. This is not a coincidence -- each position represents a power of 2.
With 1 bit: 0 or 1 = 2 values
With 2 bits: 00, 01, 10, 11 = 4 values
With 3 bits: 000 through 111 = 8 values
With 4 bits: 0000 through 1111 = 16 values
With 8 bits (1 byte): 0 through 255 = 256 values
With N bits: 2^N values (always a power of 2)
Each position in a binary number represents a power of 2, starting from 2^0 on the right. To convert, multiply each digit by its power of 2 and add them all up.
Divide the number by 2 repeatedly. Write down the remainder each time. Read the remainders bottom-to-top.
Instead of dividing, just find the largest power of 2 that fits, subtract it, and repeat:
Convert 200 to binary:
200 - 128 = 72 (128 fits, write 1) --> 1_______
72 - 64 = 8 (64 fits, write 1) --> 11______
8 - 32? No. (write 0) --> 110_____
8 - 16? No. (write 0) --> 1100____
8 - 8 = 0 (8 fits, write 1) --> 11001___
Done! Remaining positions are 0 --> 11001000
Verify: 128 + 64 + 8 = 200 ✓
Binary is great for computers but annoying for humans. The number 11010110 is hard to read. Hexadecimal (hex) solves this by grouping every 4 binary digits into one hex digit. Since 4 bits can represent 0-15, and we only have digits 0-9, we borrow letters A-F for 10-15.
Hex numbers are usually written with a 0x prefix to distinguish them from decimal. So 0xFF means "FF in hex" not "the letters FF."
Group the binary digits into chunks of 4 (from the right), then convert each chunk:
Each hex position is a power of 16. Multiply and add:
Octal uses digits 0-7. Each octal digit represents exactly 3 binary digits. You will mostly see octal in Unix/Linux file permissions (like chmod 755).
In Linux, file permissions are 3 groups of 3 bits: owner, group, others.
7 = 111 = read(4) + write(2) + execute(1) = all permissions (owner)
5 = 101 = read(4) + execute(1) = read and execute (group)
5 = 101 = read(4) + execute(1) = read and execute (others)
A bit is a single 0 or 1. A byte is 8 bits grouped together. A byte can represent any value from 0 to 255 (2^8 = 256 possible values). Everything in a computer is measured in bytes.
| Type (C++) | Size | Range | Use Case |
|---|---|---|---|
bool | 1 byte | true / false | Flags, conditions |
char | 1 byte | -128 to 127 or 0-255 | Characters, raw bytes |
short | 2 bytes | -32,768 to 32,767 | Small numbers |
int | 4 bytes | -2.1 billion to 2.1 billion | Most numbers |
long long | 8 bytes | -9.2 quintillion to 9.2 quintillion | Large numbers |
float | 4 bytes | ~7 decimal digits precision | Approximate decimals |
double | 8 bytes | ~15 decimal digits precision | Precise decimals |
Bitwise AND compares two numbers bit by bit. For each position: if both bits are 1, the result is 1. Otherwise, the result is 0. Think of it as: "both must agree to let it through."
AND is like two security guards at a door. Both must say "yes" for you to get through. If either says "no," you are blocked.
n & 1 -- if result is 1, n is odd. If 0, n is even. This works because the last bit is the "1s place" -- odd numbers always have it set.color & 0xFF extracts the last 8 bits (blue channel from a color).n & 0xFFFFFFF0 clears the last 4 bits.Bitwise OR compares two numbers bit by bit. For each position: if either bit (or both) is 1, the result is 1. Only if both are 0 does the result stay 0.
OR is like two doors to a room. If either door is open, you can get in. Only if both are closed are you stuck.
n | 0x01 forces the last bit on (makes any number odd).READ | WRITE | EXECUTE combines permission flags. Each flag is a single bit, OR combines them into one value.XOR (exclusive OR) is the most interesting bitwise operation. For each position: if the bits are different, the result is 1. If they are the same, the result is 0. Think of it as a "difference detector."
You can swap two numbers using only XOR -- no temporary variable needed:
Given an array of numbers from 1 to n with one missing, XOR all of them:
1 ^ 2 ^ 3 ^ 4 ^ 5 = some value
1 ^ 2 ^ 4 ^ 5 = different value (3 is missing)
XOR the two results and you get the missing number! Because every number that appears in both will cancel out (a ^ a = 0), leaving only the missing one.
NOT flips every single bit. Every 0 becomes 1, every 1 becomes 0. It is the only bitwise operation that works on a single number (not two).
In C++ with a 32-bit int, ~0 does not give you a giant positive number. Because of two's complement (explained later), ~0 = -1. And ~5 = -6. The formula is: ~n = -(n + 1).
Shifting moves all bits left or right by a certain number of positions. Bits that "fall off" the edge are lost. New bits that enter from the other side are 0.
Shifts all bits to the left. Each left shift multiplies by 2.
Shifts all bits to the right. Each right shift divides by 2 (rounding down).
Shifts are extremely fast -- much faster than multiplication or division. The CPU can shift bits in a single clock cycle. This is why you see them in performance-critical code, game engines, and embedded systems. n << 1 is the same as n * 2 but faster.
Unsigned right shift (logical): fills vacant bits with 0. Always divides by 2ⁿ.
Signed right shift (arithmetic): fills vacant bits with the sign bit. For negative numbers, this rounds toward negative infinity, not toward zero.
In C/C++, right-shifting a negative number is implementation-defined. In Java, >> is arithmetic and >>> is logical. In Python, >> is always arithmetic (sign-preserving).
How does a computer store -5 when it only has 0s and 1s? It uses a system called two's complement. The leftmost bit is the sign bit: 0 = positive, 1 = negative.
Two steps: flip all the bits (NOT), then add 1.
With 8 bits, you can represent -128 to +127:
These are the bit tricks that show up in interviews and real systems code. Each one exploits a specific property of binary.
| Trick | Code | What It Does |
|---|---|---|
| Check if even | n & 1 == 0 | Last bit is 0 = even |
| Check if odd | n & 1 == 1 | Last bit is 1 = odd |
| Multiply by 2 | n << 1 | Shift left = double |
| Divide by 2 | n >> 1 | Shift right = halve |
| Multiply by 2^k | n << k | Shift left by k positions |
| Check if power of 2 | n & (n-1) == 0 | Powers of 2 have exactly one 1-bit |
| Get lowest set bit | n & (-n) | Isolates the rightmost 1 |
| Turn off lowest set bit | n & (n-1) | Clears the rightmost 1 |
| Toggle bit at position k | n ^ (1 << k) | Flip the kth bit |
| Set bit at position k | n | (1 << k) | Force kth bit to 1 |
| Clear bit at position k | n & ~(1 << k) | Force kth bit to 0 |
| Check bit at position k | (n >> k) & 1 | Is the kth bit set? |
Powers of 2 in binary have exactly one 1-bit:
Everything in your computer -- every photo, song, game, text message, and PDF -- is stored as a sequence of bytes. There is no "text" or "image" at the hardware level. It is all just bytes. The software decides how to interpret those bytes.
When a number needs more than 1 byte (like a 32-bit int), the question is: which byte comes first in memory? There are two conventions:
It seems backward, but little-endian has a practical advantage: the first byte always tells you if a number is even or odd (the least significant byte is at the lowest address). It also makes some hardware operations simpler. You do not need to memorize this -- just know it exists and that it explains why bytes sometimes look "reversed" in memory dumps.
ASCII assigns a number (0-127) to each character. It uses 7 bits (usually stored in 1 byte). Here are the important ranges:
ASCII only covers English (128 characters). Unicode covers every writing system on Earth -- over 150,000 characters including Chinese, Arabic, emoji, and ancient scripts. Unicode assigns each character a "code point" (like U+0041 for 'A').
UTF-8 is the most common encoding. It uses 1-4 bytes per character: ASCII characters use 1 byte (backward compatible), while other characters use 2-4 bytes. This is why a file with only English text is roughly 1 byte per character, but a file with Chinese or emoji is larger.
Every file on your computer is just a sequence of bytes. The first few bytes usually identify the file type -- this is called a magic number or file signature. Software reads these bytes to know what kind of file it is dealing with.
A PDF is not magic -- it is a structured byte sequence:
// Open a file and read its first 16 bytes
#include <fstream>
#include <iostream>
#include <iomanip>
int main() {
std::ifstream file("document.pdf", std::ios::binary);
unsigned char buffer[16];
file.read(reinterpret_cast<char*>(buffer), 16);
// Print each byte as hex
for (int i = 0; i < 16; i++) {
std::cout << std::hex << std::setw(2)
<< std::setfill('0') << (int)buffer[i] << " ";
}
// Output: 25 50 44 46 2d 31 2e 37 ... (%PDF-1.7...)
return 0;
}C++
When you "open" a file in a program, you are really just reading a stream of bytes. A text editor interprets those bytes as characters. An image viewer interprets them as pixel colors. A PDF reader interprets them as document structure. The bytes are the same -- only the interpretation changes. Understanding this makes you a fundamentally better programmer.
Here is every bitwise operator in C++ with examples you can run.
| Operator | Symbol | Example | Result |
|---|---|---|---|
| AND | & | 12 & 10 | 8 (1100 & 1010 = 1000) |
| OR | | | 12 | 10 | 14 (1100 | 1010 = 1110) |
| XOR | ^ | 12 ^ 10 | 6 (1100 ^ 1010 = 0110) |
| NOT | ~ | ~0 | -1 (all bits flipped) |
| Left shift | << | 5 << 2 | 20 (5 x 4) |
| Right shift | >> | 20 >> 2 | 5 (20 / 4) |
// Count set bits (number of 1s in binary representation)
int countBits(int n) {
int count = 0;
while (n) {
n &= (n - 1); // Turn off the lowest set bit
count++;
}
return count;
}
// Or use the built-in:
int bits = __builtin_popcount(42); // = 3 (101010 has three 1s)C++
// Use bitmask as a set of flags
const int READ = 1 << 0; // 0001 = 1
const int WRITE = 1 << 1; // 0010 = 2
const int EXECUTE = 1 << 2; // 0100 = 4
int perms = READ | WRITE; // 0011 = can read and write
bool canRead = (perms & READ) != 0; // true
bool canExec = (perms & EXECUTE) != 0; // false
perms |= EXECUTE; // 0111 = add execute
perms &= ~WRITE; // 0101 = remove writeC++
// Subset enumeration with bitmasks
// Iterate over all subsets of {A, B, C} using a 3-bit mask
for (int mask = 0; mask < (1 << 3); mask++) {
// mask goes: 000, 001, 010, 011, 100, 101, 110, 111
if (mask & (1 << 0)) cout << "A ";
if (mask & (1 << 1)) cout << "B ";
if (mask & (1 << 2)) cout << "C ";
cout << endl;
}
// Output: (empty), A, B, AB, C, AC, BC, ABCC++
// Print a number in binary (useful for debugging)
#include <bitset>
std::cout << std::bitset<8>(42) << std::endl; // 00101010
std::cout << std::bitset<32>(-1) << std::endl; // 11111111111111111111111111111111C++
Test your understanding. Click an answer to check it.