Bitwise NOT and Two's Complement

Why ~5 equals -6 — and why that is actually the correct answer

Open a Python REPL or a JavaScript console. Type ~5. You expect the bits of 5 flipped — 0b0101 becomes 0b1010, which is 10, right? Instead you get -6. This is the single most confusing result in all of bitwise operations. It is also correct, and once you understand two's complement, it makes perfect sense. Here is why.

How Computers Represent Negative Numbers

Humans write negative numbers with a minus sign: -5. Computers cannot store a minus sign — they only have 0s and 1s. Three encoding schemes have been used historically:

SchemeHow -5 is Stored (8-bit)Problem
Sign-magnitude10000101Two zeros (+0 and -0); subtraction needs separate circuit
Ones' complement11111010Still two zeros; addition needs end-around carry
Two's complement11111011Single zero; addition and subtraction use the same circuit

Two's complement won because it simplifies the hardware. The ALU can add any two numbers — positive or negative — with the exact same adder circuit. No special cases, no separate subtraction unit. Every modern CPU, GPU, and microcontroller uses two's complement.

How Two's Complement Works

The rule to get the negative of a number: flip all bits, then add 1. For 5 in 8-bit binary:

 5 = 00000101
~5 = 11111010   (flip all bits — ones' complement)
+1 = 11111011   (add 1 — this is -5 in two's complement)

# Verify: 5 + (-5) should equal 0
  00000101  (5)
+ 11111011  (-5)
-----------
 100000000  (9 bits — the carry out is discarded, result = 0)

The key insight: in two's complement, the most significant bit (leftmost) is the sign bit. If it is 0, the number is positive or zero. If it is 1, the number is negative. For an N-bit number, the range is -2N-1 to 2N-1-1. An 8-bit signed integer goes from -128 to 127. A 32-bit signed integer goes from -2,147,483,648 to 2,147,483,647.

Why ~x = -x - 1

Now the NOT result makes sense. NOT flips every bit. Flipping every bit of a number gives you the ones' complement form of its negative — but two's complement also adds 1. So:

~x = -(x + 1)    # The NOT identity
# or equivalently:
~x = -x - 1

# Examples:
~0  = -1   (all bits become 1, which is -1 in two's complement)
~1  = -2   (0b0001 → 0b1110 = -2)
~5  = -6   (0b0101 → 0b1010 → see below)
~(-1) = 0  (~0b1111... = 0b0000...)

# In 4-bit two's complement:
# 5 = 0101, ~5 = 1010
# What is 1010 in two's complement?
# Sign bit=1, so it's negative.
# 1010 → flip(0101) → add 1 → 0110 = 6 → it's -6
# Indeed: 5's bits flipped = -6. ~5 = -6. Correct.

This identity — ~x = -x - 1 — is universal across all languages and all bit-widths that use two's complement. Memorize it. It explains every surprising NOT result.

8-bit vs 32-bit NOT: Why Width Matters

The NOT of a number depends on how many bits you are working with. The same value in different widths produces different NOT results because the sign bit is in a different position.

# 8-bit NOT of 5
5  = 0b00000101
~5 = 0b11111010  # = -6 in 8-bit two's complement → decimal: 250 unsigned

# 32-bit NOT of 5
5  = 0b00000000_00000000_00000000_00000101
~5 = 0b11111111_11111111_11111111_11111010  # = -6

# In both cases ~5 = -6! The NOT identity holds regardless of width.
# But if you treat the result as unsigned:
# 8-bit ~5 as unsigned:  250
# 32-bit ~5 as unsigned: 4294967290
# Same bit pattern, different interpretation.

This is why JavaScript's bitwise operators are all 32-bit — they convert operands to 32-bit signed integers, apply the operation, and convert back. Python's arbitrary-precision integers mean ~5 conceptually flips an infinite number of leading zeros to ones, yielding negative results consistently via the same -x-1 formula.

Practical Use 1: Creating "All 1s" Masks with ~0

~0 is the simplest way to create a value where every bit is 1. In signed interpretation it is -1. As an unsigned mask it means "all bits set." This is useful when you need a mask that passes everything through an AND, or when initializing a variable to "all valid."

# Create a byte with all 8 bits set
all_ones = (~0) & 0xFF  # → 0b11111111 = 255
# The & 0xFF is needed to truncate to 8 bits

# In C, for a 32-bit register mask:
uint32_t enable_all = ~0u;  // 0xFFFFFFFF
// Every interrupt, every DMA channel, every peripheral — enabled

Practical Use 2: Color Inversion

NOT inverts every bit, which for RGB color values gives the photographic negative. For 24-bit color (8 bits per channel), NOT of a pixel value produces the exact inverted color used in image editors.

// CSS: invert a 24-bit RGB color
let color = 0x336699;  // a blue-gray
let inverted = (~color) & 0xFFFFFF;  // → 0xCC9966 (warm gold)

// 0x33 → ~0x33 = 0xCC
// 0x66 → ~0x66 = 0x99
// 0x99 → ~0x99 = 0x66
// The & 0xFFFFFF clears bits 24-31 from the NOT

This is the same operation behind CSS filter: invert(1) — each pixel's R, G, and B channels are individually bitwise NOT'd.

Practical Use 3: Clearing Bits with AND + NOT

To turn off a specific bit while leaving all others untouched, AND with a mask that has a 0 at that position and 1s everywhere else. NOT creates that mask from a single-bit pattern. The idiom x & ~(1 << n) clears bit n of x — used in every device driver and firmware codebase.

// C: Clear bit 3 while preserving everything else
uint8_t flags = 0b00001111;   // bits 0-3 are all set
flags &= ~(1 << 3);          // clear bit 3 → 0b00000111

// (1 << 3) = 0b00001000
// ~(1 << 3) = 0b11110111  (all bits set EXCEPT bit 3)
// flags & 0b11110111 → clears bit 3, keeps bits 0-2

This is the canonical way to disable interrupts, clear status flags, or mask out configuration bits in embedded C. Without NOT, you would need to manually write the inverted mask — error-prone when bit positions change.

The JavaScript ~indexOf Pattern

JavaScript developers sometimes use ~ as a shorthand for checking if indexOf() found a match. indexOf returns the index (0 or greater) on success and -1 on failure. Since ~(-1) = 0 (falsy) and ~(any non-negative) != 0 (truthy), ~str.indexOf(substr) is truthy exactly when the substring is found:

// JavaScript: the ~indexOf trick
let str = "hello world";

if (~str.indexOf("world")) {
    // Found! ~(6) = -7, which is truthy
    console.log("contains 'world'");
}

if (!~str.indexOf("foo")) {
    // Not found: ~(-1) = 0, which is falsy
    console.log("does not contain 'foo'");
}

// Modern alternative (preferred): str.includes("world")

This is considered a code smell in 2026 — .includes() and .startsWith() are clearer — but you will encounter it in older codebases and minified libraries. Knowing ~x = -x - 1 tells you exactly why it works.

NOT is the simplest bitwise operator — one input, one output, flip everything — but its behavior is entirely determined by the number representation underneath. Once you internalize two's complement, ~5 = -6 stops being surprising and starts being the only reasonable answer. Experiment with NOT and all other bitwise operators on our free bitwise calculator, or read the complete bitwise operations guide for the full picture.