In Chapters 1 and 2 we learned how integers are encoded in binary. Now we will begin to look at operations that work directly on those bit patterns. Bitwise operators are applied position by position: each output bit depends only on the corresponding input bits at the same position. There is no dependence between positions. Operations like carry, borrowing, etc are not used. This is what distinguishes bitwise operators from standard arithmetic operations like addition. There are four standard bitwise operators: AND, OR, XOR, and NOT.
A useful mental model for bitwise operators like AND, OR, and XOR is to think of
one operand as the value and the other as a mask.
The mask determines which positions are affected and how.
This will become more intuitive as we progress to Chapters 4 (bit shifts) and 6 (masks).
In the demos below, we define bitwise operations between two operands A and B.
AND produces 1 only where both inputs are 1. We can think of AND as a filter. If we think of one operand as the value, and the other as a mask, then AND is simply a filter that blocks or allows bits. Wherever the mask has a 1, the value’s bit passes through unchanged. Wherever the mask has a 0, the output is forced to 0, and that bit is blocked. This makes AND useful for reading and testing bits: a mask with a single 1 can isolate that one bit from the rest.
The first two presets show AND as a range selector: a mask of 00001111
passes only the bottom four bits, while 11110000 passes only the top four.
This is how a program extracts a field from a packed integer. We will see this later in Chapter 7.
The even check preset uses the single-bit mask 00000001:
since only bit 0 determines parity, A & 1 returns 0 for even
values and 1 for odd ones. All higher bits are powers of 2 greater than 1, so
they contribute nothing to whether the total is even or odd.
The n & (n−1) preset is a well-known trick in bit manipulation:
subtracting 1 from any integer clears its lowest set bit and fills all lower positions with 1s, so
applying AND on the two values erases exactly that bit. The result is zero if and only if
n was a power of two.
OR produces 1 wherever either input is 1. We can think of OR as a setter. The output is 0 only when both inputs are 0. Where the mask has a 1, the output bit is forced to 1 regardless of the original bit value. Where the mask has a 0, the input bit passes through unchanged. This is the complement of AND’s filter role. Instead of clearing bits, OR sets them.
The set high 4 bits preset uses OR to activate the top four bit positions while leaving the bottom four unchanged. This is a common way to force a group of bits to be on without disturbing the rest.
The ‘A’ → ‘a’ preset illustrates that binary
sequences can encode more than integers — characters, colours, flags, and more.
Computers store characters as binary
representations, and in the ASCII encoding, every uppercase letter differs from its
lowercase counterpart by exactly one bit (bit 5). OR with 0x20 sets
that bit, converting any uppercase letter to lowercase in a single trivial operation.
Another aside: 0x20 is 32 in decimal, or 00100000 in binary,
which is bit 5, and nothing else.
The ASCII table was designed so that every uppercase letter and its lowercase counterpart
differ by exactly 32. 'A' is 65 (01000001) and 'a' is 97 (01100001).
The only difference is bit 5.
This holds for the entire alphabet: 'B'/'b', 'Z'/'z', etc.
It was a deliberate choice by the ASCII committee in the 1960s so that case conversion could be done with a
single bitwise operation rather than a lookup table.
XOR (exclusive OR) produces 1 where the inputs differ. We can think of XOR as a toggler. The output is 1 when the inputs differ, 0 when they are the same. Where the mask has a 1, the bit is flipped. Where the mask has a 0, the bit passes through unchanged. XOR has one important property: equal inputs always produce 0. A ⊕ A = 0 for any A, because every bit cancels its twin.
The flip all bits preset sets the mask to 11111111, which
flips every position in A. The result is identical to NOT, but achieved with
a binary operator rather than a unary one.
The toggle bit 0 preset flips only position 0, switching a number between even and odd.
The A ⊕ A = 0 preset demonstrates one of XOR’s defining identities: when both operands are the same value, every bit cancels its twin and the result is always zero. This appears often in low-level code when a value needs to be zeroed quickly.
The case flip preset revisits the ASCII encoding from the OR demo: where
OR with 0x20 could only convert uppercase to lowercase, XOR with
0x20 goes in either direction. It toggles bit 5, so uppercase
becomes lowercase and lowercase becomes uppercase in a single-bit operation.
NOT is the only unary bitwise operator.
A unary operator means that it only takes a single operand, so we don't really use
masks with NOT.
This operator takes that single input and simply flips every bit.
In most languages it is written ~A.
The bitwise operator NOT has the arithmetic identity ~A = −A − 1.
Since every bit in A is the complement of the corresponding bit in ~A, we can see that
A + ~A always produces all-ones — each position contributes exactly one 1 with no carry.
In two’s complement, all-ones equals −1, so A + ~A = −1, which gives
~A = −A − 1.
We can relate this to the flip-and-add-1 operation used to find the negative of any number (see the demo in Chapter 2).
AND, OR, XOR, and NOT are the four logical bitwise operators. Using the mask mental model, we can think of AND, OR, XOR as operators that use bit masks to filter, set, and toggle bits at selected positions. The operator NOT is a unary operator (no mask) that simply flips all bits. Together, these four logical operators cover every way to select, force, flip, or invert bits at a chosen position. We will see this in more detail in upcoming chapters.
In Chapter 4 we will look at shifts, a related family of operations that move the entire bit pattern left or right. Chapter 5 will then show how shifts produce masks, and Chapter 6 will put those masks to work on individual bits.