The formula
An n-bit two's complement word is read exactly like unsigned binary, except the most significant bit carries a negative weight:
value = −b(n−1)·2^(n−1) + b(n−2)·2^(n−2) + … + b(1)·2 + b(0)
One bit changed its sign; everything else is ordinary binary. That single change produces the range:
−2^(n−1) … +2^(n−1) − 1
One more negative value than positive — 8 bits give −128 to +127, 16 bits give −32,768 to +32,767, 32 bits give −2,147,483,648 to +2,147,483,647.
Negation is a bit flip and an increment:
−x = NOT(x) + 1 = 2^n − x
Invert every bit, add one — no special sign-handling circuit required.
a − b = a + NOT(b) + 1 (mod 2^n)
Subtraction is addition of the complement. The same adder does add, subtract, signed, and unsigned; only the interpretation of the bits differs.
Worked in 4 bits: 6 = 0110, so −6 = NOT(0110) + 1 = 1001 + 1 = 1010. Check against the weight formula: −8 + 2 = −6. And 20 − 60 in 8 bits: 20 + NOT(60) + 1 = 0001 0100 + 1100 0011 + 1 = 1101 1000 = −128 + 88 = −40.
Where you meet it
- The DAQ bench, staring at a garbage strain trace. Bipolar ADCs report their codes in either two's complement or offset binary, and the driver setting has to match the part. Get it wrong and the trace looks perfectly healthy — right up until the signal crosses zero, where it jumps rail to rail. That discontinuity at mid-scale is the signature; check the code format before you check the wiring.
- A life test or thermal soak that dies at an odd hour. A 32-bit millisecond tick counter used as a signed value goes negative at
2³¹ − 1ms — 24.9 days in. Any logic written asif (now > deadline)quietly inverts at the wrap. Long-duration stands hit this in the field precisely because nobody's lab checkout runs for three and a half weeks. - The review board, walking an ICD. Every 16-bit signed field in a telemetry packet is a promise that the physical quantity stays inside ±32,767 counts for the whole envelope. Ariane 5's first flight was destroyed 37 seconds after liftoff on June 4, 1996, when a horizontal-velocity term — sized for Ariane 4's gentler trajectory — overflowed a 64-bit-float-to-16-bit-signed-integer conversion in reused inertial reference software [6][7]. The conversion had been left unprotected to save processor margin; the resulting exception halted both inertial units [7].
- A code review of mixed signed/unsigned C.
if (len − 1 < n)with an unsignedlenof zero evaluates0 − 1as 4,294,967,295, and the branch goes the wrong way with no warning at runtime.
How it works
The trick underneath is that an n-bit register does arithmetic modulo 2^n whether you asked for that or not. Two's complement simply relabels the top half of the unsigned range: any pattern with the high bit set is read as its unsigned value minus 2^n. Take the byte 1001 0110 — as unsigned it is 150; as signed it is 150 − 256 = −106. Add 100 + 50 in 8 bits and the hardware produces that same pattern: correct as an unsigned 150, wrapped to −106 if you declared the variable signed. The bits never lie; the declaration decides what they mean.
Because the relabeling costs nothing, the ALU runs one add instruction for both interpretations and reports two different fault indicators. The carry flag signals unsigned overflow — the result outran 2^n. The overflow flag signals signed trouble — two operands of the same sign produced a result of the opposite sign, as in 32,767 + 1 = −32,768 on 16 bits. Assembly programmers pick the right conditional branch for the declared type; everyone else trusts the compiler to. A counter that "went negative" on the stand is this mechanism, every time.
Three sharp edges are worth memorizing. First, the range is lopsided, so the most negative value is its own negation: NOT(1000 0000) + 1 = 0111 1111 + 1 = 1000 0000. Negating −128 in 8 bits returns −128, and abs() of the most negative integer fails at every width. Fault-detection code that negates raw sensor values needs to know this. Second, widening a value requires sign extension — copy the top bit into the new bits — or the byte holding −1 (0xFF) becomes 255 in a 16-bit field. The same distinction splits arithmetic right shift (copies the sign bit in) from logical right shift (feeds zeros in); shifting −8 right by one gives −4 with one and a large positive number with the other. Third, and least intuitive: the hardware wraps deterministically, but C and C++ still define signed overflow as undefined behavior. C++20 and C23 finally mandated two's complement representation after decades of nominal machine diversity — yet both deliberately kept overflow undefined so optimizers can assume it never happens [8][9]. A guard written as if (x + 1 < x) tests a condition the compiler is entitled to declare impossible and delete. Test the operands against the limits before the operation, not the wreckage after it.
The wraparound also has a legitimate use. Free-running timestamp counters are compared correctly by subtracting first and interpreting the difference as signed — (int32_t)(now − then) gives the true elapsed ticks across a wrap, as long as the real interval stays under half the counter range. Compare the raw values directly and the logic breaks at every rollover. The same modular arithmetic sets the famous deadline for 32-bit signed Unix time: 2³¹ − 1 seconds after January 1, 1970 lands at 03:14:07 UTC on January 19, 2038. Where wrap is unacceptable and range is tight — control loops, DSP accumulators — processors offer saturating arithmetic instead, which pins at the rail rather than jumping across it. A pinned actuator command is a degraded state; a sign-flipped one is an event report.
History
Two's complement arrived with the stored-program computer itself. In June 1945, John von Neumann's First Draft of a Report on the EDVAC — the mimeographed document Herman Goldstine circulated to two dozen people, and the first published description of the stored-program architecture — specified binary arithmetic with two's complement, on the grounds that it simplified subtraction [1][2]. Maurice Wilkes's team at Cambridge, working directly from that report, built EDSAC, which ran its first program on May 6, 1949 and carried negative numbers in two's complement from the start [1][3].
The matter was not settled for another two decades. UNIVAC, Control Data, and DEC's early machines — the 1107, the CDC 6600, the PDP-1 — used ones' complement instead, which negates by bit-flip alone but pays for it with an end-around carry and two representations of zero, an all-zeros one and an all-ones one that software had to treat as equal [1][4]. IBM's System/360, announced April 7, 1964, put two's complement in the machine that came to define commercial computing, and the minicomputers and microprocessors that followed all fell in line [1][5]. The paperwork lagged the hardware by a lifetime: C++20 and then C23 finally struck ones' complement and sign-magnitude from the language standards, roughly seventy-five years after the First Draft — while pointedly leaving signed overflow undefined [8][9].
Related tools
- /tools/adc-resolution — n bits buy
2^ncodes; bipolar converters hand them to you in two's complement - /tools/snr-enob — effective number of bits, where the signed-integer bookkeeping meets the analog noise floor
Sources
- https://en.wikipedia.org/wiki/Two%27s_complement
- https://en.wikipedia.org/wiki/First_Draft_of_a_Report_on_the_EDVAC
- https://en.wikipedia.org/wiki/EDSAC
- https://en.wikipedia.org/wiki/Ones%27_complement
- https://en.wikipedia.org/wiki/Signed_number_representations
- https://en.wikipedia.org/wiki/Ariane_flight_V88
- http://sunnyday.mit.edu/nasa-class/Ariane5-report.html
- https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0907r4.html
- https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2412.pdf