Loading learning content...
If setting bits is activation, clearing bits is deactivation. This operation—forcing a specific bit to 0 while leaving all others untouched—is essential for revoking permissions, disabling features, removing elements from sets, and resetting state flags.
Clearing bits is more nuanced than setting them. While OR with 1 naturally sets a bit, there's no single operator that naturally clears a bit. We need a combination: create an inverted mask, then AND. This pattern introduces the bitwise NOT operator and reveals the elegant duality between AND and OR operations.
In this page, we'll develop a deep understanding of bit clearing, explore why the AND-with-inverted-mask pattern works, handle edge cases, and see how this operation powers revocation and cleanup in real systems.
By the end of this page, you will understand: (1) The formula n & (1 << i) for clearing bit i, (2) Why bitwise AND with an inverted mask guarantees selective clearing, (3) The role of bitwise NOT () in creating inverted masks, (4) The idempotent nature of bit clearing—clearing an already-clear bit changes nothing, (5) The duality between SET (OR with mask) and CLEAR (AND with inverted mask), and (6) Real-world applications in permission revocation and state reset.
Clearing a bit means forcing a specific bit position to 0, regardless of its current value.
Given:
n (our target number)i (the bit position to clear)Goal:
i is guaranteed to be 0The Formula:
result = n & ~(1 << i)
This formula has three components:
(1 << i) — Creates a mask with only bit i set to 1~(1 << i) — Inverts the mask so all bits except i are 1, and bit i is 0n & mask — Performs bitwise AND to preserve all bits where mask is 1, clear where mask is 0Let's trace through this step by step.
Example: Clear bit 3 in the number 13 Step 1: Start with n = 13Binary: 0 0 0 0 1 1 0 1 7 6 5 4 3 2 1 0 ← bit positions Step 2: Create initial mask with (1 << 3)1: 0 0 0 0 0 0 0 11 << 3: 0 0 0 0 1 0 0 0 ← mask has only bit 3 set Step 3: Invert the mask with ~(1 << 3)mask: 0 0 0 0 1 0 0 0~mask: 1 1 1 1 0 1 1 1 ← inverted: bit 3 is 0, all others are 1 Step 4: AND the number with the inverted maskn = 13: 0 0 0 0 1 1 0 1~mask: 1 1 1 1 0 1 1 1 & ─────────────────Result: 0 0 0 0 0 1 0 1 = 5 Bit 3 is now cleared (was 1, now 0)All other bits unchangedThe bitwise NOT operator (~) flips every bit in a number: 0 becomes 1, and 1 becomes 0. This creates the "complement" of the number.
NOT Truth Table:
| A | ~A |
|---|---|
| 0 | 1 |
| 1 | 0 |
Simple, but with an important catch: NOT applies to ALL bits, including leading zeros that you might not be thinking about. In a 32-bit integer, ~1 isn't 0—it's 0xFFFFFFFE (-2 in two's complement).
Bitwise NOT in 8-bit representation: Original: 0 0 0 0 1 0 0 0 = 8 (1 << 3) ~After NOT: 1 1 1 1 0 1 1 1 = 247 (unsigned) or -9 (signed) In 32-bit:1 << 3 = 0x00000008 = 0000...1000~(1<<3) = 0xFFFFFFF7 = 1111...0111 The NOT creates a mask where:- All bits are 1 EXCEPT position 3- Position 3 is 0 This is exactly what we need for clearing!In two's complement, ~n = -(n+1). So ~0 = -1 (all bits set), ~1 = -2, ~7 = -8. When you see a negative number after NOT, don't panic—the binary pattern is correct, but the signed interpretation is negative. For clearing bits, the binary pattern is what matters.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
// Understanding bitwise NOT // In JavaScript, ~ operates on 32-bit signed integersconsole.log(~0); // -1 (all 32 bits are 1)console.log(~1); // -2 (0xFFFFFFFE)console.log(~(-1)); // 0 (flipping all 1s gives all 0s) // Viewing the actual bit patternfunction showBits(n: number, label: string): void { // Use >>> 0 to treat as unsigned for display const unsigned = n >>> 0; console.log(`${label}: ${n} (binary: ${unsigned.toString(2).padStart(32, '0')})`);} showBits(8, "8");showBits(~8, "~8");// 8: 00000000000000000000000000001000// ~8: 11111111111111111111111111110111 (-9 signed, but perfect mask) // Creating clearing masksfunction createClearMask(position: number): number { return ~(1 << position);} console.log("\nClearing masks:");for (let i = 0; i < 8; i++) { const mask = createClearMask(i); // Show low 8 bits for readability console.log(`Position ${i}: ${(mask & 0xFF).toString(2).padStart(8, '0')} (low 8 bits)`);}// Position 0: 11111110// Position 1: 11111101// Position 2: 11111011// Position 3: 11110111// ... etc ---python# Understanding bitwise NOT # Python handles integers with arbitrary precision# ~ in Python: ~n = -(n+1) due to two's complement print(~0) # -1 (conceptually: all bits are 1, infinitely)print(~1) # -2print(~(-1)) # 0 (flipping -1 which is all 1s gives 0) # For clear visualization, mask to 32 bitsdef show_bits(n: int, label: str, bits: int = 32): masked = n & ((1 << bits) - 1) # Mask to 'bits' bits print(f"{label}: {n} (binary: {bin(masked)[2:].zfill(bits)})") show_bits(8, "8")show_bits(~8, "~8")# 8: 00000000000000000000000000001000# ~8: -9, masked shows: 11111111111111111111111111110111 # Creating clearing masksdef create_clear_mask(position: int, bits: int = 8) -> int: mask = ~(1 << position) # Mask to specified bits for display return mask & ((1 << bits) - 1) print("\nClearing masks (8-bit):")for i in range(8): mask = create_clear_mask(i) print(f"Position {i}: {bin(mask)[2:].zfill(8)}")# Position 0: 11111110# Position 1: 11111101# Position 2: 11111011# ... etcUnderstanding why n & ~(1 << i) clears bit i requires examining the AND truth table carefully.
AND Truth Table:
| A | B | A AND B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
Key Insight: When you AND with 0, the result is always 0, regardless of the original value. When you AND with 1, the original value is preserved.
This is the exact opposite of OR's behavior:
Our inverted mask has:
Understanding AND behavior at each bit position: Original n: 1 0 1 1 1 0 1 0 (n = 186)~(1 << 4): 1 1 1 0 1 1 1 1 (inverted mask: bit 4 is 0) ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ For each position, apply AND:Pos 7: 1 AND 1 = 1 (preserved: mask is 1)Pos 6: 0 AND 1 = 0 (preserved: mask is 1)Pos 5: 1 AND 1 = 1 (preserved: mask is 1)Pos 4: 1 AND 0 = 0 (CLEARED: mask is 0) ← This is the clear!Pos 3: 1 AND 1 = 1 (preserved: mask is 1)Pos 2: 0 AND 1 = 0 (preserved: mask is 1)Pos 1: 1 AND 1 = 1 (preserved: mask is 1)Pos 0: 0 AND 1 = 0 (preserved: mask is 1) Result: 1 0 1 0 1 0 1 0 (n = 170) Only bit 4 changed (1→0). All other bits unchanged.SET and CLEAR are dual operations: SET uses OR to force a 1, CLEAR uses AND to force a 0. SET uses a mask with a 1 at the target, CLEAR uses a mask with a 0 at the target. They're mirror images of each other.
Like setting, clearing is idempotent: clearing a bit that's already 0 has no effect. The result is identical to the input.
This property ensures:
Clearing bit 1 in a number where bit 1 is ALREADY clear: n = 5: 0 0 0 0 0 1 0 1 (bits 0, 2 are set; bit 1 is clear)~(1 << 1): 1 1 1 1 1 1 0 1 (mask to clear bit 1) & ─────────────────Result: 0 0 0 0 0 1 0 1 = 5 (no change!) 0 AND 0 = 0, so clearing an already-clear bit keeps it as 0. This is idempotence: clear(clear(n, i), i) === clear(n, i) Mathematical property: n & mask & mask === n & mask123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
// Clear bit at position i// Returns a new value with bit i guaranteed to be 0 function clearBit(n: number, i: number): number { return n & ~(1 << i);} // In-place modificationfunction clearBitInPlace(value: { n: number }, i: number): void { value.n &= ~(1 << i); // Equivalent to: value.n = value.n & ~(1 << i)} // Examples demonstrating idempotenceconst n = 13; // Binary: 1101 (bits 0, 2, 3 are set)console.log(`Original: ${n} = ${n.toString(2)}`); console.log(`Clear bit 0: ${clearBit(n, 0)} = ${clearBit(n, 0).toString(2)}`);// 12 = 1100 (bit 0 was 1, now 0) console.log(`Clear bit 1: ${clearBit(n, 1)} = ${clearBit(n, 1).toString(2)}`);// 13 = 1101 (bit 1 was already 0, no change - idempotent) console.log(`Clear bit 2: ${clearBit(n, 2)} = ${clearBit(n, 2).toString(2)}`);// 9 = 1001 (bit 2 was 1, now 0) console.log(`Clear bit 3: ${clearBit(n, 3)} = ${clearBit(n, 3).toString(2)}`);// 5 = 0101 (bit 3 was 1, now 0) ---python# Clear bit at position i# Returns a new value with bit i guaranteed to be 0 def clear_bit(n: int, i: int) -> int: return n & ~(1 << i) # Examples demonstrating idempotencen = 13 # Binary: 1101 (bits 0, 2, 3 are set)print(f"Original: {n} = {bin(n)}") print(f"Clear bit 0: {clear_bit(n, 0)} = {bin(clear_bit(n, 0))}")# 12 = 0b1100 (bit 0 was 1, now 0) print(f"Clear bit 1: {clear_bit(n, 1)} = {bin(clear_bit(n, 1))}")# 13 = 0b1101 (bit 1 was already 0, no change - idempotent) print(f"Clear bit 2: {clear_bit(n, 2)} = {bin(clear_bit(n, 2))}")# 9 = 0b1001 (bit 2 was 1, now 0) # Demonstrating repeated clearing (idempotent)result = nresult = clear_bit(result, 3)result = clear_bit(result, 3) # Clear againresult = clear_bit(result, 3) # And againprint(f"After clearing bit 3 three times: {result} = {bin(result)}")# 5 = 0b101 (same as clearing once) ---javapublic class ClearBit { // Clear bit at position i // Returns a new value with bit i guaranteed to be 0 public static int clearBit(int n, int i) { return n & ~(1 << i); } public static void main(String[] args) { int n = 13; // Binary: 1101 System.out.println("Original: " + n + " = " + Integer.toBinaryString(n)); System.out.println("Clear bit 0: " + clearBit(n, 0)); // 12 System.out.println("Clear bit 1: " + clearBit(n, 1)); // 13 (idempotent) System.out.println("Clear bit 2: " + clearBit(n, 2)); // 9 System.out.println("Clear bit 3: " + clearBit(n, 3)); // 5 // In-place with compound assignment int m = 13; m &= ~(1 << 3); // Clear bit 3 m &= ~(1 << 3); // Clear again (no effect) System.out.println("After clearing bit 3 twice: " + m); // 5 }} ---cpp#include <iostream>#include <bitset> // Clear bit at position i// Returns a new value with bit i guaranteed to be 0int clearBit(int n, int i) { return n & ~(1 << i);} int main() { int n = 13; // Binary: 1101 std::cout << "Original: " << n << " = " << std::bitset<8>(n) << std::endl; std::cout << "Clear bit 0: " << clearBit(n, 0) << " = " << std::bitset<8>(clearBit(n, 0)) << std::endl; // 12 std::cout << "Clear bit 1: " << clearBit(n, 1) << " = " << std::bitset<8>(clearBit(n, 1)) << std::endl; // 13 (idempotent) std::cout << "Clear bit 2: " << clearBit(n, 2) << " = " << std::bitset<8>(clearBit(n, 2)) << std::endl; // 9 std::cout << "Clear bit 3: " << clearBit(n, 3) << " = " << std::bitset<8>(clearBit(n, 3)) << std::endl; // 5 // In-place with compound assignment int m = 13; m &= ~(1 << 3); // Clear bit 3 m &= ~(1 << 3); // Clear again (no effect) std::cout << "After clearing bit 3 twice: " << m << std::endl; // 5 return 0;}Just as with setting, clearing multiple bits can be done sequentially or with a combined mask.
Approach 1: Sequential Clearing
n = n & ~(1 << i);
n = n & ~(1 << j);
n = n & ~(1 << k);
Approach 2: Combined Mask (More Efficient)
mask = (1 << i) | (1 << j) | (1 << k); // Bits to clear
n = n & ~mask; // Invert once, AND once
The combined approach is more efficient:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
// Clearing multiple bits efficiently // Approach 1: Sequential (works but less efficient)function clearBitsSequential(n: number, positions: number[]): number { for (const pos of positions) { n = n & ~(1 << pos); } return n;} // Approach 2: Combined mask (more efficient)function clearBitsWithMask(n: number, positions: number[]): number { let clearMask = 0; for (const pos of positions) { clearMask |= (1 << pos); // Build mask of bits to clear } return n & ~clearMask; // Invert once, AND once} // Approach 3: Predefined mask for known positionsconst CLEAR_BITS_1_3_5 = ~((1 << 1) | (1 << 3) | (1 << 5)); function clearKnownBits(n: number): number { return n & CLEAR_BITS_1_3_5;} // Examplesconst n = 0xFF; // All 8 bits set: 11111111console.log(`Original: ${n.toString(2).padStart(8, '0')}`); console.log(`Clear 1,3,5 (sequential): ${clearBitsSequential(n, [1, 3, 5]).toString(2).padStart(8, '0')}`);// 11010101 = 213 console.log(`Clear 1,3,5 (combined): ${clearBitsWithMask(n, [1, 3, 5]).toString(2).padStart(8, '0')}`);// 11010101 = 213 // Visual demonstrationfunction visualizeClear(n: number, positions: number[]): void { console.log(`\nClearing positions [${positions}] from ${n.toString(2).padStart(8, '0')}`); let mask = 0; for (const pos of positions) { mask |= (1 << pos); } console.log(`Set mask: ${mask.toString(2).padStart(8, '0')}`); console.log(`Inverted: ${((~mask) & 0xFF).toString(2).padStart(8, '0')}`); console.log(`Result: ${(n & ~mask).toString(2).padStart(8, '0')}`);} visualizeClear(0xFF, [2, 4, 6]); ---python# Clearing multiple bits efficiently # Approach 1: Sequential (works but less efficient)def clear_bits_sequential(n: int, positions: list[int]) -> int: for pos in positions: n = n & ~(1 << pos) return n # Approach 2: Combined mask (more efficient)def clear_bits_with_mask(n: int, positions: list[int]) -> int: clear_mask = 0 for pos in positions: clear_mask |= (1 << pos) # Build mask of bits to clear return n & ~clear_mask # Invert once, AND once # Approach 3: Functional stylefrom functools import reducedef clear_bits_functional(n: int, positions: list[int]) -> int: clear_mask = reduce(lambda acc, pos: acc | (1 << pos), positions, 0) return n & ~clear_mask # Examplesn = 0xFF # All 8 bits set: 11111111print(f"Original: {bin(n)[2:].zfill(8)}") result1 = clear_bits_sequential(n, [1, 3, 5])print(f"Clear 1,3,5 (sequential): {bin(result1)[2:].zfill(8)}")# 11010101 = 213 result2 = clear_bits_with_mask(n, [1, 3, 5])print(f"Clear 1,3,5 (combined): {bin(result2)[2:].zfill(8)}")# 11010101 = 213 # Visual demonstrationdef visualize_clear(n: int, positions: list[int]) -> None: print(f"\nClearing positions {positions} from {bin(n)[2:].zfill(8)}") mask = 0 for pos in positions: mask |= (1 << pos) print(f"Set mask: {bin(mask)[2:].zfill(8)}") print(f"Inverted: {bin((~mask) & 0xFF)[2:].zfill(8)}") print(f"Result: {bin((n & ~mask) & 0xFF)[2:].zfill(8)}") visualize_clear(0xFF, [2, 4, 6])A common operation is clearing a range of consecutive bits. This requires building a mask that covers the entire range, then inverting and ANDing.
Clear bits from position low to high (inclusive):
rangeMask = ((1 << (high - low + 1)) - 1) << low;
result = n & ~rangeMask;
Let's break this down:
(high - low + 1) = number of bits in range(1 << count) - 1 = all 1s for that count<< low = shift to correct starting position~rangeMask = invert so range is 0s, rest is 1sn & ~rangeMask = clear the range1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
// Clear a range of bits from position low to high (inclusive) function clearBitRange(n: number, low: number, high: number): number { // Create mask with 1s in positions [low, high] const count = high - low + 1; const rangeMask = ((1 << count) - 1) << low; // Invert and AND to clear return n & ~rangeMask;} // Visual demonstrationfunction visualizeClearRange(n: number, low: number, high: number): void { const count = high - low + 1; const rangeMask = ((1 << count) - 1) << low; const result = n & ~rangeMask; console.log(`Clear bits [${low}, ${high}] in ${n.toString(2).padStart(8, '0')}`); console.log(`Range count: ${count}`); console.log(`Range mask: ${rangeMask.toString(2).padStart(8, '0')}`); console.log(`Inverted: ${((~rangeMask) & 0xFF).toString(2).padStart(8, '0')}`); console.log(`Result: ${result.toString(2).padStart(8, '0')}`);} // Examplesconsole.log("Example 1: Clear bits 2-4 in 0xFF");visualizeClearRange(0xFF, 2, 4);// Original: 11111111// Range mask: 00011100// Result: 11100011 console.log("\nExample 2: Clear bits 0-3 in 0xFF (clear low nibble)");visualizeClearRange(0xFF, 0, 3);// Original: 11111111// Result: 11110000 console.log("\nExample 3: Clear bits 4-7 in 0xFF (clear high nibble)");visualizeClearRange(0xFF, 4, 7);// Result: 00001111 // Specialized functions for common casesfunction clearLowNBits(n: number, count: number): number { const mask = (1 << count) - 1; // Low 'count' bits set return n & ~mask;} function clearHighNBits(n: number, count: number, totalBits: number = 32): number { const mask = ((1 << count) - 1) << (totalBits - count); return n & ~mask;} console.log("\nClear low 4 bits of 0xFF:", clearLowNBits(0xFF, 4).toString(2).padStart(8, '0'));// 11110000 ---python# Clear a range of bits from position low to high (inclusive) def clear_bit_range(n: int, low: int, high: int) -> int: """Clear bits from position low to high (inclusive).""" count = high - low + 1 range_mask = ((1 << count) - 1) << low return n & ~range_mask # Visual demonstrationdef visualize_clear_range(n: int, low: int, high: int) -> None: count = high - low + 1 range_mask = ((1 << count) - 1) << low result = n & ~range_mask print(f"Clear bits [{low}, {high}] in {bin(n)[2:].zfill(8)}") print(f"Range count: {count}") print(f"Range mask: {bin(range_mask)[2:].zfill(8)}") print(f"Inverted: {bin((~range_mask) & 0xFF)[2:].zfill(8)}") print(f"Result: {bin(result)[2:].zfill(8)}") # Examplesprint("Example 1: Clear bits 2-4 in 0xFF")visualize_clear_range(0xFF, 2, 4)# Original: 11111111# Result: 11100011 print("\nExample 2: Clear bits 0-3 in 0xFF (clear low nibble)")visualize_clear_range(0xFF, 0, 3)# Result: 11110000 # Specialized functionsdef clear_low_n_bits(n: int, count: int) -> int: """Clear the lowest 'count' bits.""" mask = (1 << count) - 1 return n & ~mask def clear_high_n_bits(n: int, count: int, total_bits: int = 32) -> int: """Clear the highest 'count' bits in a total_bits integer.""" mask = ((1 << count) - 1) << (total_bits - count) return n & ~mask print(f"\nClear low 4 bits of 0xFF: {bin(clear_low_n_bits(0xFF, 4))[2:].zfill(8)}")# 11110000Clearing bits is the undo operation for setting—it revokes, disables, and removes. Let's explore practical applications.
Permission systems use bit clearing to revoke capabilities from users or roles.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
// Complete permission system with grant and revoke enum Permission { READ = 0, WRITE = 1, DELETE = 2, ADMIN = 3, AUDIT = 4,} const PERM = { READ: 1 << Permission.READ, WRITE: 1 << Permission.WRITE, DELETE: 1 << Permission.DELETE, ADMIN: 1 << Permission.ADMIN, AUDIT: 1 << Permission.AUDIT,}; class User { constructor(public name: string, public permissions: number = 0) {} grant(perm: number): this { this.permissions |= perm; // SET return this; } revoke(perm: number): this { this.permissions &= ~perm; // CLEAR return this; } has(perm: number): boolean { return (this.permissions & perm) === perm; } listPermissions(): string[] { return Object.entries(PERM) .filter(([_, bit]) => this.permissions & bit) .map(([name]) => name); }} // Usage demonstrationconst bob = new User("Bob"); console.log(`Bob starts with: [${bob.listPermissions()}]`); // Grant multiple permissionsbob.grant(PERM.READ).grant(PERM.WRITE).grant(PERM.DELETE);console.log(`After grants: [${bob.listPermissions()}]`); // Security incident! Revoke write and deletebob.revoke(PERM.WRITE).revoke(PERM.DELETE);console.log(`After revocation: [${bob.listPermissions()}]`); // Revoke again (idempotent - no change)bob.revoke(PERM.WRITE);console.log(`After re-revocation: [${bob.listPermissions()}]`); // Output:// Bob starts with: []// After grants: [READ, WRITE, DELETE]// After revocation: [READ]// After re-revocation: [READ]SET and CLEAR are mirror operations. Understanding their duality deepens your bit manipulation intuition.
| Aspect | SET Bit | CLEAR Bit |
|---|---|---|
| Formula | n | (1 << i) | n & ~(1 << i) |
| Operator | OR (|) | AND (&) with NOT (~) |
| Mask value at target | 1 | 0 |
| Mask value elsewhere | 0 | 1 |
| Effect at target | Force to 1 | Force to 0 |
| Effect elsewhere | Preserve | Preserve |
| Idempotent? | Yes | Yes |
| Compound operator | |= | &= ~ |
1234567891011121314151617181920212223242526272829303132333435363738
// Demonstrating the duality of SET and CLEAR function demonstrateDuality(n: number, position: number): void { const bit = (n >> position) & 1; const set = n | (1 << position); const clear = n & ~(1 << position); console.log(`n = ${n} (${n.toString(2).padStart(8, '0')})`); console.log(`Bit at position ${position}: ${bit}`); console.log(`After SET: ${set} (${set.toString(2).padStart(8, '0')})`); console.log(`After CLEAR: ${clear} (${clear.toString(2).padStart(8, '0')})`); console.log();} demonstrateDuality(0b10101010, 3); // Bit 3 is already 1// SET keeps it 1, CLEAR makes it 0 demonstrateDuality(0b10101010, 2); // Bit 2 is 0// SET makes it 1, CLEAR keeps it 0 // The operations are inverses in a sense:// Starting from any bit state:// - SET then CLEAR = bit is 0// - CLEAR then SET = bit is 1// The last operation always wins const n = 0b10101010;const pos = 4; let result = n;result |= (1 << pos); // SETresult &= ~(1 << pos); // CLEARconsole.log(`SET then CLEAR: bit is ${(result >> pos) & 1}`); // 0 result = n;result &= ~(1 << pos); // CLEAR result |= (1 << pos); // SETconsole.log(`CLEAR then SET: bit is ${(result >> pos) & 1}`); // 1You've now mastered the bit-clearing operation—the complement to bit-setting. Let's consolidate the key concepts.
n & ~(1 << i) clears bit i to 0 while preserving all other bits unchanged.~ inverts all bits, creating a mask with 0 at the target and 1s everywhere else.n & mask & mask === n & mask.n &= ~mask for clear, in-place modification.| Operation | Formula | Example (n=15) |
|---|---|---|
| Clear bit i | n & ~(1 << i) | 15 & ~(1<<1) = 13 |
| Clear bits i,j | n & ~((1<<i) | (1<<j)) | 15 & ~... = 9 |
| Clear low k bits | n & ~((1<<k) - 1) | 15 & ~15 = 0 |
| Clear range [l,h] | n & ~(((1<<(h-l+1))-1)<<l) | Complex |
What's Next:
With checking, setting, and clearing mastered, we now turn to the most elegant operation: toggling a bit at position i—flipping a bit from 0 to 1 or 1 to 0 with a single operation. This is powered by the XOR operator and has unique properties that neither SET nor CLEAR possess.
You can now clear any bit at any position to 0 with surgical precision. This operation enables revocation, deactivation, and field clearing. Next, we'll learn the toggle operation, which combines the effects of set and clear based on the current state.