
Let's embark on a journey to learn bitwise operations! These are fundamental in many areas of computing, from low-level programming and embedded systems to optimizing algorithms and graphics.
We'll cover the core concepts, operators, common use cases, and provide examples to solidify your understanding.
Understanding the Basics: Bits and Binary
Before diving into operations, it's crucial to understand what bits are and how numbers are represented in binary.
Bit: The smallest unit of data in a computer, representing either a
0
or a1
.Binary: A base-2 numbering system, using only the digits 0 and 1. Each position in a binary number represents a power of 2.
Example:The decimal number 5 is represented as 0101
in binary (assuming an 8-bit representation, it's 00000101
).
0 * 2^7 + 0 * 2^6 + 0 * 2^5 + 0 * 2^4 + 0 * 2^3 + 1 * 2^2 + 0 * 2^1 + 1 * 2^0
0 + 0 + 0 + 0 + 0 + 4 + 0 + 1 = 5
The Bitwise Operators
There are six main bitwise operators in most programming languages (C, C++, Java, JavaScript, Python, etc.). They operate on numbers at the individual bit level.
Let's use two example 8-bit numbers for demonstration:
A = 01011010
(decimal 90)B = 00110110
(decimal 54)
1. Bitwise AND (&
)
Rule: The resulting bit is
1
if both corresponding bits are1
. Otherwise, it's0
.Truth Table:
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1
Example:
A: 01011010 B: 00110110 ---------- A & B: 00010010 (decimal 18)
Common Uses:
Checking if a specific bit is set:
(number & (1 << bitPosition)) != 0
Masking (clearing) specific bits: Setting bits to
0
.number & ~mask
(where~mask
creates a mask with0
s at the positions to clear).Extracting a subset of bits: As seen in this example
(0 >> 4 & 15)
to extract specific nibbles (4 bits).
2. Bitwise OR (|
)
Rule: The resulting bit is
1
if at least one of the corresponding bits is1
. Otherwise, it's0
.Truth Table:
0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1
Example:
A: 01011010 B: 00110110 ---------- A | B: 01111110 (decimal 126)
Common Uses:
Setting specific bits:
number | (1 << bitPosition)
Combining flags/permissions: Each bit represents a different option or permission, and you
OR
them together.
3. Bitwise XOR (^
) (Exclusive OR)
Rule: The resulting bit is
1
if the corresponding bits are different. Otherwise, it's0
.Truth Table:
0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0
Example:
A: 01011010 B: 00110110 ---------- A ^ B: 01101100 (decimal 108)
Common Uses:
Toggling (flipping) specific bits:
number ^ (1 << bitPosition)
Swapping two numbers without a temporary variable:
a = a ^ b; b = a ^ b; // (a^b)^b = a a = a ^ b; // (a^b)^a = b (original a)
Detecting differences between two values.
Simple encryption/checksums.
4. Bitwise NOT (~
) (One's Complement)
Rule: Flips every bit.
0
becomes1
, and1
becomes0
.Example:
A: 01011010 --------- ~A: 10100101
Important Note on Signed Numbers: For signed numbers, the most significant bit (leftmost) indicates the sign (0 for positive, 1 for negative). Applying
~
to a positive number usually results in a negative number, due to two's complement representation.In 8-bit,
~0 = -1
,~1 = -2
,~2 = -3
, etc. This is because~x
often results in-(x + 1)
.
Common Uses:
Creating masks:
~0
creates a mask of all1
s.~mask
inverts a mask.Used with other operations for clearing or setting specific bits.
5. Left Shift (<<
)
Rule: Shifts all bits to the left by a specified number of positions. New bits on the right are filled with
0
s. Bits shifted off the left are discarded.Mathematical Equivalence: Shifting left by
n
positions is equivalent to multiplying by (as long as there's no overflow).Example:
A: 01011010 (decimal 90) A << 1: 10110100 (decimal 180) - Multiplied by 2 A << 2: 01101000 (decimal 104) - Multiplied by 4 (with overflow, as 90 * 4 = 360, but 8-bit max is 255)
Common Uses:
Multiplying by powers of 2 efficiently.
Creating bit masks dynamically:
1 << n
creates a mask with a single1
at then
-th position (0-indexed).Preparing data for other bitwise operations.
6. Right Shift (>>
) (Signed Right Shift / Arithmetic Right Shift)
Rule: Shifts all bits to the right by a specified number of positions. Bits shifted off the right are discarded. The behaviour of the bits shifted in on the left depends on whether the number is signed or unsigned.
Signed Right Shift (Arithmetic Right Shift): The most significant bit (sign bit) is copied into the new positions on the left. This preserves the sign of the number. (Commonly
>>
in C/C++/Java/JavaScript).Unsigned Right Shift (Logical Right Shift): New bits on the left are always filled with
0
s, regardless of the sign bit. (Commonly>>>
in Java/JavaScript, less common in C/C++ without explicit casting to unsigned).
Mathematical Equivalence: Shifting right by
n
positions is equivalent to integer division by .Example (Signed Right Shift
>>
):Positive Number:
A: 01011010 (decimal 90) A >> 1: 00101101 (decimal 45) - Divided by 2
Negative Number (e.g., -90 in 8-bit two's complement is 10100110):
N: 10100110 (decimal -90) N >> 1: 11010011 (decimal -45) - Sign bit (1) is replicated
Example (Unsigned Right Shift
>>>
in JS/Java):Negative Number:
N: 10100110 (decimal -90) N >>> 1: 01010011 (decimal 83) - Filled with 0s, sign is lost
Common Uses:
Dividing by powers of 2 efficiently.
Extracting specific bits or groups of bits: As seen in your example
e >> 4
.Parsing bit fields from data structures.
Key Concepts and Techniques
Bit Masks
A bit mask is a binary number used in conjunction with bitwise operators to selectively set, clear, or check specific bits in another number.
To Check if a bit is set:
JavaScriptconst number = 0b10101100; // 172 const bitPosition = 2; // (0-indexed) checking the 3rd bit from right const mask = 1 << bitPosition; // 0b00000100 (4) if ((number & mask) !== 0) { console.log(`Bit ${bitPosition} is set.`); // Output: Bit 2 is set. } else { console.log(`Bit ${bitPosition} is not set.`); }
To Set a bit:
JavaScriptlet number = 0b10100000; // 160 const bitToSet = 3; const mask = 1 << bitToSet; // 0b00001000 (8) number = number | mask; // 0b10101000 (168) console.log(number.toString(2)); // Output: 10101000
To Clear a bit:
JavaScriptlet number = 0b10101000; // 168 const bitToClear = 3; const mask = 1 << bitToClear; // 0b00001000 (8) number = number & ~mask; // 0b10100000 (160) console.log(number.toString(2)); // Output: 10100000
Explanation of
~mask
: Ifmask
is00001000
,~mask
in an 8-bit system would be11110111
. When youAND
this withnumber
, all bits remain the same except for the 3rd bit, which is forced to0
.To Toggle a bit:
JavaScriptlet number = 0b10101000; // 168 const bitToToggle = 3; const mask = 1 << bitToToggle; // 0b00001000 (8) number = number ^ mask; // 0b10100000 (160) - Toggles it off console.log(number.toString(2)); // Output: 10100000 number = number ^ mask; // 0b10101000 (168) - Toggles it back on console.log(number.toString(2)); // Output: 10101000
Practical Applications
Graphics and Image Processing: Manipulating individual pixel components (RGBA values), color transformations.
Networking: Parsing network packets, setting and checking flags in headers.
Embedded Systems/Microcontrollers: Controlling hardware registers, reading sensor data, managing I/O pins.
Game Development: Collision detection (using bitmasks for shapes), optimizing game states.
Cryptography: Many cryptographic algorithms rely heavily on bitwise operations.
Data Compression: Encoding/decoding data at the bit level.
Optimizing Performance: Bitwise operations are often much faster than their arithmetic counterparts (multiplication/division by powers of 2).
Tips for Mastering Bitwise Operations
Think in Binary: Always visualize the numbers in their binary representation when working with bitwise operators. Draw them out if needed.
Practice, Practice, Practice: The more you use them, the more intuitive they become. Start with simple problems and gradually increase complexity.
Understand Number Representation: Be aware of how signed and unsigned numbers are represented (two's complement is standard for signed integers). This is crucial for understanding
~
and>>
.Know Your Language's Specifics: While the core operators are universal, some languages might have subtle differences (e.g.,
>>>
in JS/Java).Use Tools: Online binary converters or programming language debuggers that show binary representations can be very helpful.
Let's Do Some Exercises!
To test your understanding, try to solve these (mentally or with pen and paper first, then use code to verify):
What is
0b11001010 & 0b00111100
?What is
0b10101010 | 0b01010101
?What is
0b11110000 ^ 0b00110011
?If
num = 0b00110101
, what isnum << 3
?If
num = 0b10000000
(8-bit, signed), what isnum >> 1
?How would you write a function to check if a number is a power of 2 using bitwise operations? (Hint: Consider
n & (n - 1)
)How would you clear the most significant bit (MSB) of an 8-bit number
num
?
Once you've tried these, feel free to share your answers, and we can discuss them further! Mastering bitwise operations will open up a new level of understanding and control in your programming journey.