A signed eight-bit number holds values from −128 to 127. The range is not symmetrical, because zero has to live somewhere and it occupies one of the 256 available patterns on the non-negative side.
That asymmetry is not a quirk of one language. It follows from two’s complement, which is how essentially every processor represents negative numbers, and it produces the one arithmetic case that has no valid answer: negating the most negative value gives itself back.
How does two’s complement work?
By inverting every bit and adding one. That is the whole rule, and its virtue is that addition then works identically for signed and unsigned values — the processor needs one adder rather than two.
The top bit ends up indicating the sign, which is a consequence rather than the design. A number with its high bit set is negative, and the remaining bits are not simply its magnitude.
The alternative schemes tried early on — a separate sign bit, or inverting without adding one — both produce two representations of zero and need special handling in the adder. Two’s complement has exactly one zero and needs none.
What are the ranges?
Symmetrical about zero except for that extra value at the bottom.
| Width | Signed range | Unsigned range |
|---|---|---|
| 8 bits | −128 to 127 | 0 to 255 |
| 16 bits | −32,768 to 32,767 | 0 to 65,535 |
| 32 bits | ±2.1 billion | 0 to 4.3 billion |
| 64 bits | ±9.2 quintillion | 0 to 18.4 quintillion |
The 32-bit signed limit is the one that shows up most often in the wild, because it is the boundary behind the 2038 timestamp problem and behind a long history of counters that stopped at about 2.1 billion.
Overflow wraps rather than erroring in most languages. Adding one to the largest value gives the smallest, silently, which is why a counter can go from a large positive number to a large negative one between two readings.
What do the operators do?
Work on each bit position independently, which is what makes them fast and what makes them useful for flags. AND keeps a bit only if both inputs have it, OR keeps it if either does, and XOR keeps it if exactly one does.
That gives the standard idioms. AND with a mask extracts particular bits, OR with a mask sets them, XOR with a mask flips them, and AND with an inverted mask clears them. A set of boolean options packed into one integer is manipulated entirely this way.
XOR has a property the others lack: applying it twice returns the original. That is why it appears in simple ciphers, in checksums, and in the trick for swapping two values without a temporary.
Why is shifting not always division?
Because of the sign bit. Shifting left by one multiplies by two until bits fall off the top; shifting right by one divides by two, and what arrives at the top depends on which shift you used.
An arithmetic right shift copies the sign bit inward, so negative numbers stay negative. A logical right shift brings in zeros, which turns a negative number into a large positive one. Languages differ in which they give you, and getting the wrong one on signed data is a classic bug.
Even the arithmetic version is not quite division for negative values, because it rounds towards negative infinity rather than towards zero. Minus one shifted right stays minus one.
Where does this still matter?
Anywhere data is packed tightly or read from something that packed it. Network protocols, file formats, hardware registers, permission bits and compression schemes all use fields narrower than a byte, and reading them means masking and shifting.
It also matters for anything counting. A value stored in a fixed width has a ceiling, and identifying that ceiling in advance is easier than diagnosing the day a total goes negative.
For ordinary application code the operators are rarely the right tool — a set of named booleans is clearer than a packed integer, unless something else defined the layout.
Why is unsigned worse than it sounds?
Because it moves the failure rather than removing it. An unsigned value cannot go negative, so a subtraction that would have produced minus one produces the largest representable number instead — a loop counting down past zero runs billions of times rather than stopping.
Mixing signed and unsigned in one expression is the related trap. Many languages convert the signed value to unsigned rather than the other way round, so a comparison between a negative number and an unsigned one can come out backwards.
The practical guidance is to use signed types for anything that is arithmetic and unsigned only where the value genuinely is a bit pattern rather than a quantity.
Questions people ask
Why does negating the smallest value fail? Its positive counterpart does not exist in the range, so the operation returns the same number.
Is XOR a cipher? Only with a key as long as the message and never reused, which is the one-time pad. Otherwise it is trivially broken.
Do these work the same in every language? The operators do. Shift behaviour on signed values and on shifts larger than the width varies.
What is the fastest way to test a bit? AND with a mask holding just that bit, and compare against zero.
Work it out in binary and check. The bitwise calculator shows each operation across the bit positions, the bit shift calculator covers the shifts including the sign question, and the two’s complement calculator converts between a signed value and its bit pattern.