Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
2’s Complement Examples: Negation, Overflow, Sign Extension and Sensor Data
Thirteen worked examples with bit diagrams and verified C programs: negating, decoding, subtraction by addition, signed and unsigned overflow detection, 12-bit sign extension and Cortex-M4 flags.
2’s Complement Examples: What This Page Covers
This page turns the theory into practice. Every example works with the same idea: 2’s complement examples are easiest when you can see the bits, so each one has a diagram, the arithmetic, and (where relevant) a C program that was compiled and run to produce the output you see.
| # | Example | Skill |
|---|---|---|
| 1 | Compute −x by invert and add 1 | Negation |
| 2 | Decode a negative byte (two ways) | Reading signed values |
| 3 | −1 in 8, 16 and 32 bits | All ones |
| 4 | One bit pattern, many interpretations (C program) | Type decides meaning |
| 5 | Subtraction by addition | Why the ALU works |
| 6 | Signed overflow detection (worked and in C) | Spotting wrong results |
| 7 | Unsigned overflow detection | Carry |
| 8 | Computing ranges for any bit width | Min and max |
| 9 | Signed 16-bit sensor data from two bytes | Real I2C pattern |
| 10 | Sign-extending a 12-bit value | Odd-width fields |
| 11 | Counting loops with signed vs unsigned | Classic bug |
| 12 | APSR flags N, Z, C, V by example | Cortex-M4 internals |
| 13 | Mini exercises | Self-test |
Example 1: Computing −x by Inverting and Adding 1
Problem: What is the 8-bit 2’s complement representation of −13?
Check: 0xF3 as unsigned is 243, and 256 − 13 = 243. As a signed byte the weights give −128 + 64 + 32 + 16 + 2 + 1 = −13.
Table of negations
| x | x in binary | ~x | ~x + 1 | Hex | Signed | 256 − x |
|---|---|---|---|---|---|---|
| 1 | 00000001 | 11111110 | 11111111 | 0xFF | −1 | 255 |
| 5 | 00000101 | 11111010 | 11111011 | 0xFB | −5 | 251 |
| 13 | 00001101 | 11110010 | 11110011 | 0xF3 | −13 | 243 |
| 40 | 00101000 | 11010111 | 11011000 | 0xD8 | −40 | 216 |
| 100 | 01100100 | 10011011 | 10011100 | 0x9C | −100 | 156 |
| 127 | 01111111 | 10000000 | 10000001 | 0x81 | −127 | 129 |
The special case: −(−128)
Negate 1000 0000 (−128): invert gives 0111 1111, add 1 gives 1000 0000 again. The most negative value is its own negation, because +128 does not fit in a signed byte. In C, -INT8_MIN overflows (after promotion to int it is fine, but the cast back to int8_t gives −128 again), and abs(INT_MIN) is undefined.
Example 2: Decoding a Negative Byte (Two Methods)
Problem: What signed value does 0x9C (1001 1100) hold?
Method A — negative MSB weight
Method B — negate again
If the MSB is 1, the number is negative. Its magnitude is the 2’s complement of the pattern: invert 1001 1100 → 0110 0011, add 1 → 0110 0100 = 100. So the value is −100. Both methods agree.
Method C — subtract 2N (fastest for a quick check)
0x9C = 156 unsigned; 156 − 256 = −100.
| Byte | Unsigned | Signed | Method C working |
|---|---|---|---|
| 0xF3 | 243 | −13 | 243 − 256 |
| 0x9C | 156 | −100 | 156 − 256 |
| 0x80 | 128 | −128 | 128 − 256 |
| 0xFE | 254 | −2 | 254 − 256 |
| 0x7F | 127 | +127 | MSB = 0: unchanged |
Example 3: −1 in 8, 16 and 32 Bits (All Ones)
−1 is always all ones, whatever the width. Reason: 1 + (−1) must be 0, and only all-ones plus one carries all the way out and leaves zero.
| Type | Hex of −1 | Hex of minimum | Hex of maximum |
|---|---|---|---|
| int8_t | 0xFF | 0x80 | 0x7F |
| int16_t | 0xFFFF | 0x8000 | 0x7FFF |
| int32_t | 0xFFFFFFFF | 0x80000000 | 0x7FFFFFFF |
0xFFFFFFFF is often used as a “not set” marker (flash erased state is also all ones: 0xFF in every byte). Seeing 0xFFFFFFFF in a debugger can mean −1, an erased flash word, or an invalid handle: check the variable’s type.Example 4: One Bit Pattern, Many Interpretations (C Program)
The same bits give different numbers depending on type and width. This program prints the byte 0xC8 and two wider patterns in every interpretation.
Example 5: Subtraction by Addition
The ALU has no separate subtractor. It computes a − b as a + (−b), where −b is obtained by inverting b and adding 1 (the +1 is supplied as the initial carry).
9 − 5 = 9 + (−5)
5 − 9 = 5 + (−9) = −4
Notice the pattern: when the true result is non-negative, a carry out of the MSB appears and is discarded. When it is negative, there is no carry out and the sign bit is 1 automatically. The processor never needs to know which case it is in.
Example 6: Signed Overflow Detection
Problem: add 100 and 50 as signed 8-bit numbers. The true result is 150, which is above the maximum 127.
The register now holds 1001 0110 = −106 as a signed byte (150 − 256). The result is wrong because the true answer needs 9 bits as a signed number. The two operands were positive and the result is negative: that is the signature of signed overflow.
| Case | Operand signs | Result sign | Overflow? |
|---|---|---|---|
| 100 + 50 | + and + | − (wrong) | Yes |
| −100 + (−50) | − and − | + (wrong) | Yes |
| 70 + (−20) | different signs | + | Never possible |
| −128 + (−1) | − and − | +127 (wrong) | Yes (underflow) |
int8_t values already promotes them to int in C, so the exact sum is available for checking. Only the assignment back into int8_t loses information.Example 7: Unsigned Overflow Detection
For unsigned numbers the test is simpler: after sum = a + b (modulo 2N), an overflow occurred if and only if sum < a. In hardware this is the C flag, the carry-out of the MSB.
Subtraction underflows when the subtrahend is larger: check x < y before computing x - y. This is the safe pattern for byte counters, buffer lengths and tick differences.
Example 8: Computing the Range for Any Bit Width
The two formulas to remember: unsigned max = 2N − 1; signed min = −2N−1, signed max = 2N−1 − 1. They apply even to odd widths such as a 10-bit or 12-bit ADC or a 24-bit audio sample.
1 << 32 on a 32-bit int is undefined. Use 1LL (64-bit) for intermediate calculations and cast down at the end.Example 9: A Signed 16-bit Sensor Reading Built From Two Bytes
Accelerometers, gyroscopes and many temperature sensors return a 16-bit 2’s complement value in two registers. Suppose the bytes received are 0xFF then 0x9C.
Notes
- Assemble the bytes in an unsigned 16-bit variable first, then reinterpret as
int16_t. This keeps the shift well defined. - Reading the same bits as unsigned gives 65436, which would look like a huge acceleration.
- Scaling uses integer maths: multiply first (in 32 bits so it cannot overflow), divide last.
Example 10: Sign-Extending a 12-bit Value
Some peripherals deliver values narrower than a C type, for example a 12-bit signed field in a 16-bit register. Storing it in an int16_t directly would not extend the sign. We must copy bit 11 into bits 15..12.
The trick (raw ^ 0x800) - 0x800 works for any width: XOR the sign bit, then subtract it. If the sign bit was 1, the XOR clears it and the subtraction makes the result negative; if it was 0, the XOR sets it and the subtraction cancels it. There is no shifting of negative numbers and no undefined or implementation-defined behaviour.
Example 11: Counting Down: Signed vs Unsigned Loop Counters
for (uint8_t i = 3; i >= 0; i--) never ends. After i = 0, i-- wraps to 255, and 255 ≥ 0 is true again. The compiler usually warns: “comparison of unsigned expression ≥ 0 is always true”.Example 12: APSR Flags N, Z, C, V by Example (Cortex-M4)
The Application Program Status Register holds four condition flags that the ALU updates after every flag-setting instruction (ADDS, SUBS, CMP). Compilers use them to implement <, >=, ==: signed comparisons look at N and V, unsigned comparisons look at C.
| Operation (32-bit) | Result | N | Z | C | V |
|---|---|---|---|---|---|
| 0x7FFFFFFF + 1 | 0x80000000 | 1 | 0 | 0 | 1 |
| 0xFFFFFFFF + 1 | 0x00000000 | 0 | 1 | 1 | 0 |
| 5 − 5 | 0x00000000 | 0 | 1 | 1 | 0 |
| 3 − 5 | 0xFFFFFFFE | 1 | 0 | 0 | 0 |
| 5 − 3 | 0x00000002 | 0 | 0 | 1 | 0 |
| 0x80000000 − 1 | 0x7FFFFFFF | 0 | 0 | 1 | 1 |
| 0xFFFFFFFF + 0xFFFFFFFF | 0xFFFFFFFE | 1 | 0 | 1 | 0 |
- 0x7FFFFFFF + 1: two positives give a negative: V = 1 (signed overflow), C = 0 (no unsigned carry).
- 0xFFFFFFFF + 1: unsigned wrap to 0: C = 1 and Z = 1; as signed this is −1 + 1 = 0, no overflow: V = 0.
- 5 − 5: result zero: Z = 1. On subtraction C = 1 means no borrow.
- 3 − 5: a borrow was needed so C = 0; the result is negative: N = 1.
- 0x80000000 − 1: most negative minus one overflows: V = 1.
Example 13: Mini Exercises — Try It Yourself
function revealAns(btn){var d=btn.nextElementSibling,o=d.style.display===’block’;d.style.display=o?’none’:’block’;btn.textContent=o?’Show answer’:’Hide answer’;}Quick Reference Card
| Task | Recipe |
|---|---|
| Negate an N-bit number | ~x + 1 (or 2N − x) |
| Decode a signed pattern | MSB weight is −2N−1, or unsigned − 2N when MSB = 1 |
| −1 | All ones |
| Signed range | −2N−1 to 2N−1 − 1 |
| Sign-extend a K-bit field | (x ^ signbit) - signbit |
| Detect unsigned add overflow | sum < a |
| Detect signed add overflow | Same-sign operands, different-sign result (or check in a wider type) |
| Subtraction in the ALU | a + ~b + 1 |
Examples complete!
Finish Module 2 with the quiz: 2’s complement computations, signed range questions, overflow identification, code snippet challenges and interview questions.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments