Bitwise Calculator: What AND, OR, XOR and Shifts Actually Do to the Bits
About the Author

A bitwise calculator never treats your numbers as numbers. 12 AND 10 is not “twelve and ten” — it is 1100 stacked over 1010, four independent one-bit questions, and the column-by-column answer 1000 happens to read back as 8. Nothing carries from one column into the next. That single property is why these operations cost a processor one cycle, and it is also why they behave nothing like arithmetic.
Everything below comes out of that: why ~12 returns 243 in one place and −13 in another, why >> and >>> are two different instructions, the four masking lines that account for most bitwise code ever written, and the precedence rule that silently breaks x & 1 == 0 in C but not in Python.
One Column at a Time, and the Columns Never Talk
Line the operands up and every logical operation is the same procedure: take the bit from A, take the bit below it from B, look up one answer. Six operations, one lookup table:
| A | B | AND | OR | XOR | NAND | NOR | XNOR |
|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 |
| 0 | 1 | 0 | 1 | 1 | 1 | 0 | 0 |
| 1 | 0 | 0 | 1 | 1 | 1 | 0 | 0 |
| 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1 |
Run 12 & 10 through it. 12 is 0000 1100 and 10 is 0000 1010; the only column where both hold a 1 is bit 3, so the answer is 0000 1000 = 8. Change the operator and nothing else about the procedure changes: 12 | 10 is 0000 1110 = 14, and 12 ^ 10 is 0000 0110 = 6. Compare that with 12 + 10 = 22, where the units column produces a carry that has to travel left before the next column can be finished — the reason a binary addition calculator has to show carry rows and a bitwise one does not.
NAND, NOR and XNOR are simply the first three inverted, and NAND deserves a footnote: it is functionally complete, so a chip full of nothing but NAND gates can implement any logic function at all. If you want to see that expressed as an algebraic expression instead of a bit pattern, the truth table calculator builds the full table from a written formula.
Set the Width First: Why a Bitwise Calculator Returns 243, 65,523 or −13 for ~12
NOT flips every bit. The catch is the word every — the answer depends entirely on how many bits the container holds, and nothing in the expression ~12 tells you that:
| Container | ~12 in binary | Read unsigned | Read signed |
|---|---|---|---|
| 8-bit byte | 1111 0011 | 243 | −13 |
| 16-bit short | 1111 1111 1111 0011 | 65,523 | −13 |
| 32-bit int | 1111 … 1111 0011 | 4,294,967,283 | −13 |
| Python int (no width) | …1111 0011 | n/a | −13 |
Look down the last column. The signed reading is −13 at every width, and that is not a coincidence: two's complement is defined so that ~x = −x − 1 always. The unsigned reading is the one that moves, because it is just “the pattern, read as a plain binary number” and there are more leading ones to read at 32 bits than at 8.
Two's complement itself is one rule: the top bit carries a negative weight. In a byte the columns are worth −128, 64, 32, 16, 8, 4, 2, 1, so 1111 0011 totals −128 + 64 + 32 + 16 + 2 + 1 = −13. Set the calculator above to 8-bit and type −13 in decimal and you will get the same 1111 0011 back — a decimal to binary calculator that ignores width cannot show you this, because there is no such thing as a negative binary pattern without a width to hang it on.
Shifting Multiplies by Two Until a Bit Falls Off the End
x << n slides every bit n places to the left and pads with zeros, which multiplies by 2n. 13 << 3 is 0000 1101 becoming 0110 1000 = 104, exactly 13 × 8. The qualifier arrives at the top edge: 13 << 5 wants to be 416, but in a byte the two highest bits run off the end and you get 1010 0000 = 160 instead. No flag is raised. The value is simply 416 mod 256.
Right shifts are where two operations hide behind one idea. The arithmetic shift >> copies the sign bit inward so negatives stay negative; the logical shift >>> feeds in zeros and treats the pattern as unsigned. On the 8-bit value −40 (1101 1000):
- −40 >> 3 gives 1111 1011 = −5
- −40 >>> 3 gives 0001 1011 = 27
Here is the part that costs people an afternoon: an arithmetic right shift is a floor division, while the division operator truncates toward zero. They agree on positives and diverge on negatives. −41 >> 3 is −6, because floor(−5.125) is −6. But −41 / 8 in C, Java or Rust is −5. Swapping a division for a shift as an “optimisation” changes the answer for every negative input, and the test suite that only feeds it positive numbers will never notice.
Set, Clear, Toggle, Test: Four Lines That Cover Most Bitwise Code
Almost every practical use of these operators is one of a handful of idioms applied to a flags byte or a hardware register. Worth memorising rather than re-deriving:
| Goal | Expression | Why it works |
|---|---|---|
| Set bit n | x |= (1 << n) | OR can only add a 1, never remove one |
| Clear bit n | x &= ~(1 << n) | The mask is all ones except a 0 at n |
| Toggle bit n | x ^= (1 << n) | XOR with 1 flips, XOR with 0 leaves alone |
| Test bit n | (x >> n) & 1 | Slide the bit to position 0, mask off the rest |
| Keep the low byte | x & 0xFF | Zeros above bit 7, keeps bits 0–7 untouched |
| Clear the lowest set bit | x & (x − 1) | The subtraction borrows exactly through that bit |
| Isolate the lowest set bit | x & −x | −x is ~x + 1, which agrees with x in one place only |
| Round down to a multiple of 8 | x & ~7 | Zeroing the low three bits removes the remainder |
Follow one status register through: start at 150 = 1001 0110. Setting bit 3 gives 150 | 8 = 158. Clearing bit 2 gives 158 & ~4 = 158 & 251 = 154. Testing bit 4 gives (154 >> 4) & 1 = 1, so that flag is up. And 150 & 149 = 148, which is 150 with its lowest set bit — the 2 — knocked out; loop that and the number of iterations is the population count. Masks are almost always written in hex because one hex digit is exactly four bits, which is why a hexadecimal calculator is the other tool sitting open during register work.
XOR Is the Only One You Can Undo
AND and OR destroy information. If a & b comes out 0, the original a is unrecoverable — dozens of inputs produce that same output. XOR does not lose anything: (a ^ b) ^ b is a again, always. That single property explains most of where it turns up.
Parity and RAID. XOR every byte in a set and you get a parity byte. Lose any one member and XOR-ing the survivors with the parity reconstructs it exactly — that is RAID 5 in one sentence, and the same arithmetic behind a parity bit on a serial line.
Difference detection. a ^ b is 0 if and only if a equals b, and counting the set bits in a ^ b gives the Hamming distance. The ASCII pair ‘A’ = 65 = 0100 0001 and ‘a’ = 97 = 0110 0001 differ in exactly one bit, so 65 ^ 97 = 32 — the case bit, which is why c | 32 lowercases a letter and c & ~32 uppercases it.
Swapping without a temporary. The three-line a ^= b; b ^= a; a ^= b does work, and it is a fine interview answer, but it breaks the moment both names refer to the same variable — the first line zeroes it and the value is gone. A modern compiler also produces faster code from a plain temporary. Know the trick, do not ship it.
One warning, because it comes up constantly: XOR-ing a message against a repeating key is not encryption. The key length falls out of the ciphertext with basic frequency analysis. XOR is only unbreakable with a truly random key as long as the message, used exactly once.
The Same Expression, Four Different Answers
Most reports of a “wrong” bitwise result are really a disagreement about width and signedness between two languages:
| Expression | C (int32_t) | Java (int) | JavaScript | Python 3 |
|---|---|---|---|---|
| ~12 | −13 | −13 | −13 | −13 |
| 1 << 31 | undefined | −2,147,483,648 | −2,147,483,648 | 2,147,483,648 |
| 1 << 40 | undefined | 256 | 256 | 1,099,511,627,776 |
| −1 >>> 1 | no operator | 2,147,483,647 | 2,147,483,647 | no operator |
The 256 in that third row surprises everybody. Java and JavaScript mask the shift count to five bits for 32-bit operands, so a shift of 40 is performed as a shift of 40 & 31 = 8, and 1 << 8 is 256. C does not define the behaviour at all. Python has no width to overflow, so it just keeps growing.
Two portability notes worth pinning up. Python emulates a fixed width by masking — ~x & 0xFF for a byte, & 0xFFFFFFFF for 32 bits — and that is precisely what the width selector above does. JavaScript converts operands to 32-bit signed integers before every bitwise operation, which is why 2**32 | 0 is 0 and why x >>> 0 is the standard way to read a result back as unsigned.
The Precedence Trap That C Has and Python Does Not
In C, C++, Java, C# and JavaScript, the equality operators bind tighter than the bitwise ones. So this parity test:
if (x & 1 == 0) { /* even */ }
does not test anything. It parses as x & (1 == 0), which is x & 0, which is always 0 — the branch never runs, and the compiler is perfectly happy. The fix is a pair of brackets: (x & 1) == 0.
Python made the opposite choice: there, &, ^, | and the shifts all bind tighter than comparisons, so x & 1 == 0 means what it looks like. Code translated between the two languages changes meaning silently.
The other one to watch is that addition outranks shifting everywhere: x << 1 + 2 is x << 3, an eightfold multiply where you wanted double-then-add. The working rule is simple — bracket every bitwise sub-expression, every time. Nobody has ever lost an hour to a redundant pair of brackets.
When the Bit Trick Is the Slower Choice
Writing x << 3 instead of x * 8 buys nothing on any compiler released this century. Strength reduction is a first-pass optimisation: multiplication and division by constant powers of two already become shifts, and x % 16 on an unsigned value already becomes x & 15. You have traded readability for an instruction the compiler was going to emit anyway.
For signed values it is worse than a wash, because the two are not equivalent. The compiler emits an extra correction step for x / 2 precisely so that negatives round toward zero, which is what the language specifies. Replacing it with x >> 1 removes that step and changes the answer, as the −41 case above shows.
Where bitwise operations genuinely earn their place: hardware registers and protocol headers, where fields are packed into fixed bit ranges; bitsets and bitboards, where 64 booleans live in one register and a single AND tests all of them; hashing and checksums; and permission flags. In every one of those, the bit layout is the data structure, not an optimisation of one. For the algebraic side of the same logic — simplifying an expression before it becomes a circuit — the Boolean algebra calculator and the Karnaugh map calculator pick up where the bit patterns stop, and the binary calculator handles the ordinary arithmetic side.
For the formal definitions, operator tables and the per-language notes in one place, the Wikipedia entry on bitwise operations is a solid reference, and the two's complement article covers the sign-bit weighting in more depth than the table above.



