Three Ways to Write −5 in Binary, and Why a Two's Complement Calculator Uses This One
About the Author

Ask ten people to write −5 in eight bits and most produce 10000101 — a 1 for the minus sign, then 5 — but a two's complement calculator returns 11111011, and the gap between those two answers is not a formatting quirk. It is a hardware decision. The obvious encoding needs a comparator, a subtractor and a special case for negative zero. The one every processor actually uses needs an adder and nothing else.
Sign-Magnitude, Ones' Complement, Two's Complement: One Number, Three Patterns
All three were built in real machines. The IBM 7090 used sign-magnitude, the CDC 6600 and the PDP-1 used ones' complement, and the System/360 settled on two's complement in 1964. Here is the same handful of values in each, at eight bits:
| Value | Sign-magnitude | Ones' complement | Two's complement |
|---|---|---|---|
| +5 | 0000 0101 | 0000 0101 | 0000 0101 |
| −5 | 1000 0101 | 1111 1010 | 1111 1011 |
| −1 | 1000 0001 | 1111 1110 | 1111 1111 |
| 0 | 0000 0000 / 1000 0000 | 0000 0000 / 1111 1111 | 0000 0000 |
| −128 | — | — | 1000 0000 |
Two rows carry the whole argument. The zero row: the first two encodings each burn a second pattern on −0, so every equality test in the machine has to check for it, and the 1000 0000 that means “negative zero” in sign-magnitude is a live, useful value in two's complement. The −128 row is the payoff — that recovered pattern becomes the extra negative number the other two cannot express.
The decisive property is not in the table at all. In two's complement, ordinary unsigned addition is already correct for signed values. Add 1111 1011 (−5) to 0000 0111 (+7) with the same circuit that runs a binary addition calculator, throw away the carry that falls off the top, and you get 0000 0010 — positive 2. Sign-magnitude needs to compare magnitudes first and decide which way the subtraction goes. Ones' complement needs an end-around carry, feeding the discarded carry back into the bottom bit. Two's complement needs nothing.
The Sign Bit Is a Column Weight, Not a Flag
Here is the sentence that makes everything else fall out. In an eight-bit two's complement number the columns are not 128, 64, 32, 16, 8, 4, 2, 1. They are −128, 64, 32, 16, 8, 4, 2, 1. Nothing else changes. You read the number the same way you always did, by adding up the columns that hold a 1.
1011 0101 = −128 + 32 + 16 + 4 + 1 = −75
That is the entire definition, and it explains three things at once. Any pattern starting with 1 is negative, because −128 outweighs everything to its right (64 + 32 + 16 + 8 + 4 + 2 + 1 = 127, one short). All-ones is −1, because −128 + 127 = −1. And zero appears exactly once, because there is no separate sign to set. Flip the sign bit in the calculator above and the value jumps by exactly 128 — not by a change of sign, which is the tell that it is a weight and not a flag.
This is also why the same bit pattern has two perfectly valid readings. 1011 0101 is −75 signed and 181 unsigned, and the bits are identical — only the declared type differs. A binary to decimal calculator has to ask you which one you meant before it can answer.
Invert and Add One: Why a Two's Complement Calculator Is Really Subtracting From 2ⁿ
Everyone learns the recipe: to negate, flip every bit and add 1. Almost nobody is told why it works, which is a shame, because the reason is two lines long.
Flipping every bit of an n-bit value x gives you 2n − 1 − x. That is not a trick — the all-ones pattern is 2n − 1, and inverting x is the same as subtracting it from all ones, column by column, with no borrowing. Add 1 and you have 2n − x. Since the hardware only keeps n bits, everything is arithmetic modulo 2n, and 2n − x is congruent to −x. The “negative” number is literally the additive inverse: x + (2n − x) = 2n, which is 1 followed by n zeros, and the 1 falls off the end.
Walk it through for −75 at eight bits:
- Write 75 in binary: 0100 1011
- Invert every bit: 1011 0100 — this is 180, and indeed 255 − 75 = 180
- Add one: 1011 0101 = 181, and 256 − 75 = 181
Check the claim: 0100 1011 + 1011 0101 = 1 0000 0000. Nine bits, of which eight are kept, so the machine sees zero. 75 and −75 sum to zero, which is what “negative” means. The step-by-step panel in the calculator runs this for whatever value you type, and if you only want plain positive conversions the decimal to binary calculator covers those without the sign machinery.
A useful consequence: negating twice returns the original, and the shortcut works from either direction. Given 1011 0101 and asked what negative number it is, invert and add one to get 0100 1011 = 75, then attach the minus sign. Same recipe, no second rule to memorise.
Why the Range Stops at 127 but Reaches −128
Eight bits give 256 patterns. One is zero. That leaves 255 to split between positives and negatives, and 255 is odd, so the split cannot be even. Two's complement gives the extra one to the negatives, because the sign bit already sorts the patterns into 128 with a leading 1 and 128 with a leading 0 — and zero lives on the positive side.
| Width | Minimum | Maximum | Min as hex | Typical C type |
|---|---|---|---|---|
| 4 | −8 | 7 | 0x8 | nibble / teaching example |
| 8 | −128 | 127 | 0x80 | int8_t, signed char |
| 16 | −32,768 | 32,767 | 0x8000 | int16_t, short |
| 32 | −2,147,483,648 | 2,147,483,647 | 0x80000000 | int32_t, int |
| 64 | −9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 | 0x8000000000000000 | int64_t, long long |
Two patterns are worth memorising rather than deriving. The minimum is always 0x8 followed by zeros — sign bit set, nothing else. And −1 is always all Fs, at every width, which is why a hex dump full of FFFFFFFF usually means an uninitialised counter or a failed call that returned −1 rather than an enormous address. Converting those patterns by hand gets old fast; the hexadecimal to decimal calculator does the same signed reading from hex directly.
100 + 50 = −106, and the Rule That Catches It
Try it in an eight-bit register. 100 is 0110 0100, 50 is 0011 0010, and the column addition gives 1001 0110. No carry leaves the top of the register, so an unsigned reading — 150 — is perfectly fine. Read as signed, that leading 1 makes it −106.
Here is the trap: the usual carry flag misses this completely. There was no carry out. The overflow happened inside the number, when a carry from bit 6 pushed into the sign column and turned it on. So hardware watches both:
signed overflow = (carry into the sign bit) XOR (carry out of the sign bit)
In this addition a carry went in and none came out, so the two disagree and the overflow flag sets. Every ALU computes it this way, with a single XOR gate. There is an equivalent test that is easier to apply by eye: overflow can only happen when both operands share a sign and the result has the other one. Two positives that produce a negative, or two negatives that produce a positive. A positive plus a negative can never overflow, because the answer always lies between them.
Two practical notes. In C and C++ signed overflow is undefined behaviour, not wraparound, so a compiler is entitled to assume x + 1 > x is always true and optimise your check away; that assumption has produced real security bugs. Java, C#, Rust in release mode and every unsigned type in C all wrap predictably instead. Toggle between the presets above and the calculator prints the carry-in, carry-out and overflow flags for each operation so you can watch the rule fire.
Widening 8 Bits to 32 Without Changing the Value
Copy an eight-bit −75 (1011 0101) into a 32-bit register by padding with zeros and you get 181, not −75. The value changed because the sign bit moved: what was the −128 column at eight bits is just the 128 column at thirty-two. To preserve the value you copy the sign bit into every new position — sign extension:
1011 0101 → 1111 1111 1111 1111 1111 1111 1011 0101
That still reads as −75, because the new −231 column plus all the ones between it and the old sign bit collapse back to exactly the old −128. Positive values get zeros for the same reason, and the general rule is: replicate the sign bit. This is why processors have two different load instructions for a byte (movsx versus movzx on x86) and two different right shifts — the arithmetic shift shifts the sign in, the logical shift shifts zeros in, and the bitwise calculator shows the two side by side.
The classic bug: in C, plain char is signed on x86 Linux and unsigned on ARM. Feed a byte above 127 into isdigit() or use it as an array index and one platform sign-extends it into a negative number while the other does not. The same source compiles and runs correctly on your laptop and corrupts memory on the target board.
The abs() Bug Every Language Ships With
The lopsided range has one loose end that never gets tidied up: abs(−2147483648) is not 2147483648. There is no such int. Invert and add one on 0x80000000 gives back 0x80000000 — the one value that is its own negative — so in Java Math.abs(Integer.MIN_VALUE) returns Integer.MIN_VALUE, still negative, and it is documented behaviour rather than a defect. In C the same expression is undefined. Rust panics in debug builds.
Two more places the encoding leaks into everyday code. Integer division by −1 overflows for the same value on the same reasoning — on x86 it raises a hardware exception, which is one of the few ways a divide by something other than zero can crash a process. And Math.floor(x / 2) is not x >> 1 for negatives: the shift floors toward negative infinity (−7 » 1 = −4) while the division truncates toward zero (−7 / 2 = −3).
None of this is exotic. It is the same lopsided range you can see in the range meter above, showing up in three different disguises — and it is why the C++20 standard finally made two's complement the only permitted signed representation, forty years after the last ones' complement machine was sold. For the plain arithmetic side of the same bits, the binary calculator handles addition, subtraction and multiplication without the sign analysis, and Wikipedia's two's complement article collects the formal definitions and the machine history in one place.



