Introduction to Bitwise Operators
Part 4 of 11 of a serialized version of Bitmasks for Fun and Profit: Practical Applications for Web Developers
This is one of the most complex parts of this series. If you are ready for it, this is a good time to understand these operators better. If you're overwhelmed by this section, you can skip it and come back later. You don't need to understand operators at this level of detail to work through the examples in this book.
You can experiment with these operators in a Bitwise Operators Playground JS Fiddle:
https://jsfiddle.net/chris407x/0L17g2xv/
Bitwise operations are low-level operations that directly manipulate the bits of a number. In web development, we mostly use AND (`&`), OR (`|`), and Left Shift (`<<`).
Bitwise operators are usually native to the processor, meaning they are executed in a single CPU instruction, which is typically very fast. Bitwise operations are significantly faster than conditional statements because they involve simple, direct manipulation of bits rather than evaluating more complex logical expressions or branching (as with if-else statements).
Bitwise operations include:
1. AND (&)
• Description: Compares bits and returns 1 if both bits are 1; otherwise, it returns 0.
• Use Cases: Matching and filtering.
2. OR (|)
• Description: Compares bits and returns 1 if either bit is 1; if both bits are 0, it returns 0.
• Use Cases: Adding or concatenating bits: TWO_FLAGS = FLAG_1 | FLAG_2.
3. XOR (^)
• Description: Compares bits and returns 1 if the bits are different; if they are the same, it returns 0.
• Use Cases: Toggling values, encryption and decryption with symmetric key, calculating parity bits for error detection.
4. NOT (~)
• Description: Flips the bits, turning 1 into 0 and 0 into 1.
• Use Cases: Inverting flags, finding differences between bitmasks.
5. Left Shift (<<)
• Description: Shifts all bits to the left by a specified number of positions, multiplying the number by a power of 2.
• Use Cases: Setting constants, packing 2 4-bit numbers into an 8-bit number, modifying specific bits.
6. Right Shift (>>)
• Description: Shifts all bits to the right by a specified number of positions, dividing the number by a power of 2.
• Use Cases: Cryptography (with left shift), binary searches, fast division by two, manipulating hardware registers.
Bit operators are mathematical operators just like `+ - * /`
You can use ` & | ^ ~ << >>` the same way you can use basic arithmetic operators like `+ - * /`. Most languages support shorthand assignment operators. Shorthand assignment operators perform a specific operation on a variable and then reassign the result to that variable.
// JavaScript Shorthand Assignment Operators
// Regular ADDITION (+)
let x = 5,
x1 = x + 3;
x += 3;
// Output: 8 true
console.log(x,
x1 === x);
// OR (|)
let flags = 1,
flags1 = flags | 2;
flags |= 2;
// Output: 3 '11' true
console.log(flags,
flags.toString(2),
flags1 === flags);
// AND (&)
flags = 3,
flags1 = flags & 1;
flags &= 1;
// Output: 1 '1' true
console.log(flags,
flags.toString(2),
flags1 === flags);
// LEFT SHIFT (<<)
x = 5,
x1 = x << 2;
x <<= 2;
// Output: 20 '10100' true
console.log(x,
x.toString(2),
x1 === x);
// RIGHT SHIFT (>>)
let y = 20,
y1 = y >> 2;
y >>= 2;
// Output: 5 '101' true
console.log(y,
y.toString(2),
y1 === y);
Bitwise Operators Work With Mixed Radix (Number Base)
You can mix and match radix. This is handy if you are passed in an integer from an HTTP request but have an octal or binary flag. Regardless of the number system, bitwise operations always occur at the bit level. You can experiment with different Radix in the Bitwise Operators Playground JS Fiddle.
// JavaScript
// Example 1: Binary and Decimal
let bitmask = 10, flag = 0b0010;
// Output: '1010' '10'
console.log(bitmask.toString(2),
flag.toString(2));
// Output: 2 '10'
console.log(bitmask & flag,
(bitmask & flag).toString(2));
if (bitmask & flag){
console.log("Flag is set");
}
// Example 2: Binary and Octal
let b_bitmask = 0b10010001,
b_flag = 0o21;
// Output: '10010001' '10001'
console.log(b_bitmask.toString(2),
b_flag.toString(2));
// Output: 17 '10001'
console.log(b_bitmask & b_flag,
(b_bitmask & b_flag).toString(2));
if (b_bitmask & b_flag){
console.log("Flag is set");
}
// Example 3: Decimal and Hexadecimal
let c_bitmask = 145,
c_flag = 0x11;
// Output: '10010001' '10001'
console.log(c_bitmask.toString(2),
c_flag.toString(2));
// Output: 17 '10001'
console.log(c_bitmask & c_flag,
(c_bitmask & c_flag).toString(2));
if (c_bitmask & c_flag){
console.log("Flag is set");
}
// Example 4: Octal and Hexadecimal
let d_bitmask = 0o221,
d_flag = 0x31;
// Output: '10010001' '110001'
console.log(d_bitmask.toString(2),
d_flag.toString(2));
// Output: 33 '100001'
console.log(d_bitmask & d_flag,
(d_bitmask & d_flag).toString(2));
if (d_bitmask & d_flag){
console.log("Flag is set");
}
AND (&
) Matching
Use Cases for the Bitwise AND Operator:
Checking to see whether a bit/flag is set
Checking for a Partial Match
Checking for an Exact Match
Combined Matching Example
Clearing Bits Using a
0000
Mask
The bitwise AND operation (&
) compares each corresponding bit of the two numbers and returns 1
only if both bits are 1
. Otherwise, it returns 0
. This continues for each bit. AND (&
) Determines if certain bits are set (e.g., flags) by comparing a value with a mask and checking if the result is non-zero.
10010001 (bitmask = 145)
& 00110001 (flag = 49)
-----------
00010001 (result = 17)
// JavaScript
let bitmask = 0b10010001; // 145
let flag = 0b00110001; // 49
// Perform AND operation
let result = bitmask & flag;
// Output: 17 (00010001 in binary)
console.log(result);
Only the bits set in both bitmask
and flag
contribute to the result.
In our example, the flag has a bit at 25 (32, 0b00100000
) that is not present in the bitmask.
10010001 (bitmask = 145)
& 00110001 (flag = 49)
-----------
00010001 (result = 17)
00100000 (missing = 32)
Note that 17 + 32 = 49
Checking for a Partial Match
A partial match occurs if ANY bits set in
flag
are also set inbitmask
Checking for an Exact Match
An exact match occurs if ALL bits set in flag
are also set in bitmask
.
Combined Matching Example
The code checks for both a partial and an exact match between
bitmask
andflag
.If the match is not exact, the code identifies which bits in
flag
are not set inbitmask
and prints them as "Missing Bits."
// 145
let bitmask = 0b10010001;
// 49
let flag = 0b00110001;
// result: 17
console.log(bitmask & flag);
/* Result of & Operation:
10010001 145
00110001 49
--------
00010001 17
*/
// partial match
// If any bits match, the `&` operator will be non-zero.
if (bitmask & flag){
console.log("partial match");
}
// exact match
// The output of the & needs to equal flag
if ((bitmask & flag) == flag){
console.log("exact match"); // this will fail
}else{
console.log("failed exact match");
console.log("Bitmask: "
+ bitmask.toString(2).padStart(8, '0'));
console.log("Flag: "
+ flag.toString(2).padStart(8, '0'));
console.log("Matched Bits: "
+ (bitmask & flag).toString(2).padStart(8, '0'));
let missingBits = flag & ~bitmask;
console.log("Bits in flag but not in bitmask: \n"
+ missingBits.toString(2).padStart(8, '0'));
}
/* Output:
partial match
failed exact match
Bitmask: 10010001
Flag: 00110001
Matched Bits: 00010001
Bits in flag but not in bitmask:
00100000
*/
OR (|
): Combining
Use Cases for the Bitwise OR Operator
Setting Permissions with Named Constants: Merge Permissions Into a Single Value
Adding More Permissions with OR (
|=
)Combine Masks
Bitwise OR compares each bit of two binary numbers, resulting in a 1
if either of the corresponding bits is 1
. If both bits are zero, it returns 0
. This creates a more significant (larger) number. It also has the effect of turning a bit or flag "on" and can be used to combine two flags.
You may have seen bitwise OR in system settings:
<?php
// Set error reporting
// to show warnings
// and parse errors
// but not notices
error_reporting(E_WARNING | E_PARSE);
?>
php > var_dump(E_WARNING);
int(2)
php > var_dump(E_PARSE);
int(4)
php > var_dump(E_WARNING | E_PARSE);
int(6)
010 (E_WARNING = 2)
| 100 (E_PARSE = 4)
-----------
110 (E_WARNING | E_PARSE = 6)
Performing OR on binary numbers that do not share any bits (powers of two) will coincidentally produce the same result as decimal addition. However, they are different operations:
8 + 4 = 12
8 | 4 = 12
5 + 3 = 8
5 | 3 = 7
Binary Decimal Binary Decimal
101 5 1000 8
011 3 0010 4
---- -- OR (|) ---- --
111 7 1010 12
Setting Permissions with Named Constants: Merge Permissions Into a Single Value
Use the bitwise OR operator to combine permissions and the AND (&
) operator to check them. Named constants make code easier to understand.
// JavaScript
// 100 in binary
const READ = 0o4;
// 010 in binary
const WRITE = 0o2;
// 001 in binary
const EXECUTE = 0o1;
// Combine read and write permissions
let permissions = READ | WRITE;
// The `permissions` variable now holds both read and write permissions.
// Output will be 6, which is 110 in binary
console.log(permissions);
// You can now check if specific
// permission is set using the
// bitwise AND (`&`) operation:
if (permissions & READ) {
console.log(
"Read permission is set"
);
}
if (permissions & WRITE) {
console.log(
"Write permission is set"
);
}
if (permissions & EXECUTE) {
console.log(
"Execute permission is set"
);
} else {
console.log(
"Execute permission is NOT set"
);
}
/* Output:
Read permission is set
Write permission is set
Execute permission is NOT set
*/
Adding More Permissions with OR (|=
)
If you later want to add execute permission to the user, you can do so using the OR operator again. Assigning values this way will always turn the particular bit (in this example, EXECUTE
) "on."
// JavaScript
permissions |= EXECUTE;
// Output will be 7, which is 111 in binary
console.log(permissions);
Now, the permissions
variable includes read, write, and execute permissions.
Combine Masks
The allMasks
combines other masks using the OR (|
) operator. See the chapter Javascript: Keeping Track of Burrito Orders.
// JavaScript
// Masks grouping related ingredients for form creation
static shellsMask = BurritoOrder.WHITE_BURRITO_SHELL
| BurritoOrder.WHEAT_BURRITO_SHELL;
static riceMask = BurritoOrder.WHITE_RICE
| BurritoOrder.BROWN_RICE;
static proteinMask = BurritoOrder.CHICKEN
| BurritoOrder.PORK
| BurritoOrder.BEEF
| BurritoOrder.VEGGIE_PROTEIN
| BurritoOrder.SHRIMP;
static checkboxMask = BurritoOrder.CHEESE
| BurritoOrder.MILD_SALSA
| BurritoOrder.HOT_SALSA
| BurritoOrder.LETTUCE
| BurritoOrder.TOMATO;
static allMasks = BurritoOrder.shellsMask
| BurritoOrder.riceMask
| BurritoOrder.proteinMask
| BurritoOrder.checkboxMask;
XOR (^
) : Toggling
Use Cases for the Bitwise XOR Operator
Bit Flipping (Toggling)
XOR Data Encryption (Simple Symmetric Encryption)
The bitwise XOR (^
) operation compares each bit of two binary numbers and returns a 1 if the corresponding bits are different and 0 if they are the same.
10010001 (bitmask = 145)
^ 00110001 (flag = 49)
-----------
10100000 (result = 160)
// JavaScript
let bitmask = 0b10010001; // 145
let flag = 0b00110001; // 49
// Perform XOR operation
let result = bitmask ^ flag;
// Output: 160 (10100000 in binary)
console.log(result);
Bit Flipping (Toggling)
A common use of XOR is to toggle (flip) specific bits in a number. If you XOR a bit with 1
, it toggles the bit: 0
becomes 1
and 1
becomes 0
.
// JavaScript
// Binary: 1000 (decimal 8)
const DARK_MODE = 0b1000;
// Binary: 0100 (decimal 4)
const FULLSCREEN_MODE = 0b0100;
// Binary: 0010 (decimal 2)
const NOTIFICATIONS_ENABLED = 0b0010;
// Binary: 0001 (decimal 1)
const SOUND_EFFECTS = 0b0001;
// Binary: 1010 (decimal 10)
let settings = DARK_MODE
| NOTIFICATIONS_ENABLED;
// toggle full-screen mode
settings ^= FULLSCREEN_MODE;
/*
1010 10
0100 4
---- XOR ^
1110 14
*/
// Output will be 1110 (decimal 14)
console.log(
settings
.toString(2)
.padStart(4, '0')
);
//Flip it back
settings ^= FULLSCREEN_MODE;
/*
1110 14
0100 4
---- XOR ^
1010 10
*/
// Output will be 1010 (decimal 10)
console.log(
settings
.toString(2)
.padStart(4, '0')
);
NOT (~
): Flipping
Use Cases for the Bitwise NOT Operator
Negative Flags in Settings
Invert Status
Find Missing Bits
The bitwise NOT (~
) flips each bit, turning 1
s into 0
s and 0
s into 1
s.
10010001 (bitmask = 145)
~ --------
01101110 (result = 110)
The bitwise NOT operator (~
) flips all the bits of its operand, including all 32 bits in a 32-bit signed integer. This turns a positive number into a negative one, which can lead to unexpected behavior in JavaScript. Two's complement negation is effectively ~x + 1
. See the chapter on Two's Compliment for an explanation and workarounds.
Negative Flags in Settings
One practical use of the bitwise NOT operator is to invert the bits of a flag or status value. You may have seen this in settings:
<?php
// Set error reporting to show all errors
// except deprecated warnings and notices
error_reporting(E_ALL
& ~E_DEPRECATED
& ~E_NOTICE);
Invert Status
// JavaScript
// Binary: 1010 (decimal 10)
let status = 0b1010;
// Output will be 10
console.log(status);
// Output will be 1010
console.log(status.toString(2));
// Invert the status
let invertedStatus = ~status;
// Output will be -11
console.log(invertedStatus);
// Output will be binary -1011
console.log(invertedStatus.toString(2));
let doubleInverted = ~status;
// Output will be 10
console.log(doubleInverted);
// Output will be binary 1010
console.log(doubleInverted.toString(2));
Find Missing Bits
// JavaScript
let bitmask = 0b10010001; // 145
let flag = 0b00110001; // 49
// Calculate Bits that exist in the flag
// but not in the bitmask
// using the NOT (~) operator
let missingBits = (flag & ~bitmask);
console.log("Missing Bits from flag: "
+ missingBits.toString(2).padStart(8, '0'));
// Calculate Bits that exist in the bitmask
// but not in the flag
let missingBits2 = (bitmask & ~flag);
console.log("Missing Bits from bitmask: " + missingBits2.toString(2).padStart(8, '0'));
/* output:
Missing Bits from flag: 00100000
Missing Bits from bitmask: 10000000
*/
Left Shift (<<
): Multiply by Powers of 2
Use Cases for the Left Shift Operator
Multiplying by Powers of Two
Setting Specific Bits/Flags
You can create bit masks by left-shifting 1 to the desired bit position.
Color Manipulation
Left shifts are often used to manipulate pixel values, such as shifting color channels to their appropriate positions in a 32-bit color format (e.g., RGBA).
Beyond the scope of Web Development:
Cryptographic Algorithms
Like right shifts, left shifts are also used in various cryptographic algorithms.
Low-Level Programming and Embedded Systems
In embedded systems, left shifts can control hardware directly by setting or clearing specific bits in control registers.
Data Compression
Left shifts are used in algorithms that compress data by packing multiple smaller values into a single, more significant value.
The left shift (<<
) bitwise operator shifts the bits of a number to the left by a specified number of positions. Each shift to the left effectively multiplies the number by 2.
00010001 (number = 17)
<< 2 (shift by 2 positions)
--------- 17 x 4 = 68
01000100 (result = 68)
// JavaScript
let number = 0b00010001; // 17
// Perform Left Shift operation by 2 positions
let result = number << 2;
// Output: 68 (01000100 in binary)
console.log(result);
Multiplying by Powers of 2
One of the simplest and most common uses of the left shift operator is to multiply a number by a power of 2. This works by adding zeros to the right side, thus "shifting" the bits to the left.
// JavaScript
// Binary: 0000 0101
let result = 5;
// Output will be 5
// Binary: 0000 0101
console.log('decimal: ' + result);
console.log('binary: '
+ result.toString(2).padStart(8, '0'));
// Multiply result by 2^2 (4)
result <<= 2;
// Output will be 20,
// binary: 0001 0100
console.log('decimal: ' + result);
console.log('binary: '
+ result.toString(2).padStart(8, '0'));
// Multiply result by 2^2 (4)
result <<= 2;
// Output will be 80,
// Binary: 0101 1000
console.log('decimal: ' + result);
console.log('binary: '
+ result.toString(2).padStart(8, '0'));
/* Output
Observe how '101' gets shifted two to the left
to make larger numbers:
decimal: 5
binary: 00000101
decimal: 20
binary: 00010100
decimal: 80
binary: 01010000
*/
Setting Specific Bits/Flags
Imagine you have a set of flags representing different states and want to set a specific flag by its bit position. We use this technique extensively to set constants. Another way to look at 1 << x
is that you are setting a bit at position x
. You are saying "Shift 1 to the position x
."
// JavaScript
// 0b0001 (1 in decimal)
const FLAG_A = 1 << 0;
// 0b0010 (2 in decimal)
const FLAG_B = 1 << 1;
// 0b0100 (4 in decimal)
const FLAG_C = 1 << 2;
// No flags are set initially
let flags = 0;
// Add FLAG_B and FLAG_C
flags |= FLAG_B | FLAG_C;
// Output will be 6,
console.log(flags);
/*
0010 2
0100 4
---- OR (|)
0110 6
*/
Color Manipulation
In graphics programming, colors are often packed into a single integer. Each component (red, green, blue, alpha) can be manipulated using bitwise operations, including left shifts.
// JavaScript
// Define color components
// 0b00000101
let red = 5;
// 0b00001010
let green = 10;
// 0b00001111
let blue = 15;
// 0b11111111
let alpha = 255;
// Combine color components into a
// single 32-bit integer
let color = (alpha << 24)
| (red << 16)
| (green << 8)
| blue;
// Forces unsigned
//interpretation
// to use all 32 bits
// see Twos Compliment
// chapter
color = color >>> 0;
// Output each component and the final color value
console.log(
"Red (" + red + "): \n "
+ red.toString(2).padStart(8, '0')
);
console.log(
"Green (" + green + "): \n "
+ green.toString(2).padStart(8, '0')
);
console.log(
"Blue (" + blue + "): \n "
+ blue.toString(2).padStart(8, '0')
);
console.log(
"Alpha (" + alpha + "): \n "
+ alpha.toString(2).padStart(8, '0')
);
console.log(
"Combined Color (" + color + "): \n"
+ color.toString(2).padStart(32, '0')
);
/* output
Red (5):
00000101
Green (10):
00001010
Blue (15):
00001111
Alpha (255):
11111111
Combined Color (4278520335):
11111111000001010000101000001111
*/
Right Shift (>>
): Divide by Powers of 2
Use Cases for the Right Shift Operator
Dividing by Powers of Two
Extracting Specific Bits
Efficient Integer Division
Binary Search Algorithms
Cryptographic Algorithms
Low-Level Programming and Embedded Systems
Color Manipulation
The right shift (>>
) bitwise operator shifts the bits of a number to the right by a specified number of positions. Each shift to the right effectively divides the number by 2, discarding the bits that fall off on the right and filling in zeros on the left for positive numbers (or ones for negative numbers in two's complement representation).
01000100 (number = 68)
>> 2 (shift by two positions:
divide by 4)
---------
00010001 (result = 17)
// JavaScript
let number = 0b01000100; // 68
// Perform Right Shift operation
// by 2 positions
let result = number >> 2;
// Output: 17 (00010001 in binary)
console.log(result);
In JavaScript and other languages, you may use the unsigned right shift operator (>>>
). The >>>
operator in JavaScript shifts the bits of a number to the right, filling the leftmost bits with 0, regardless of the original sign of the number. The signed right shift (>>
) preserves the sign bit (propagating 1 for negative numbers).
The unsigned right shift is useful when:
You want to treat a signed number as unsigned and avoid sign propagation.
You want to shift bits while ensuring the leftmost bits are always filled with 0.
// JavaScript
// In 32-bit binary:
// 11111111111111111111111111111000
let signedNumber = -8;
// Shifts right,
// filling leftmost bit with 0
let resultSigned = signedNumber >> 1;
// Output: -4
console.log(resultSigned);
let binaryRepresentation = (resultSigned >>> 0).toString(2).padStart(32, '0');
// Output:
// 11111111111111111111111111111100
console.log(binaryRepresentation);
let resultUnsigned = signedNumber >>> 1;
// Output: 2147483644
console.log(resultUnsigned);
let binaryRepresentationUnsigned = (resultUnsigned >>> 0).toString(2).padStart(32, '0');
// Output:
// 01111111111111111111111111111100
console.log(binaryRepresentationUnsigned);
Example: Dividing by Powers of 2
One of the simplest uses of the right shift operator is efficiently dividing a number by powers of 2.
// JavaScript
// Binary: 0001 0000
let x = 16;
// Divide x by 2^2 (which is 4)
let result = x >> 2;
// Output will be 4,
// Binary: 0000 0100
console.log(result);
/*
Note how the bit shifts
2 to the right:
16 0001 0000 16
16 >> 2 0000 0100 4
*/
This is part 4 of a serialized version of my book:
Bitmasks for Fun and Profit: Practical Applications for Web Developers
All code in the book is syntax highlighted and printed in full color.
I publish several books about Guitar, Music and Programming on my Author Page on Amazon:
Follow me at Torus Head Studios!
https://torusheadstudios.com/