Developer looking at code with binary numbers on screen, signed vs unsigned integers explained

Signed vs Unsigned Integers: The Bit-Level Difference

The bit pattern 11111111 — eight ones. What number does it represent? If you answer 255, you are assuming unsigned. If you answer -1, you are assuming signed with two's complement. Both are correct, depending on context. The bits do not change. Only the interpretation does.

This distinction is responsible for some of the most frustrating bugs in C: sign extension surprises, unsigned wraparound in loops, and mixed signed/unsigned comparisons that defy intuition. Here is how it works at the bit level.

Unsigned Integers: Pure Binary Counting

An unsigned integer is straightforward: each bit contributes its positional value. Bit 0 adds 1, bit 1 adds 2, bit 2 adds 4, and so on. An N-bit unsigned integer ranges from 0 to 2^N - 1.

Bit Pattern (8-bit)Unsigned ValueHex
0000000000x00
0000000110x01
011111111270x7F
100000001280x80
111111102540xFE
111111112550xFF

Use unsigned when your value cannot be negative: array indices, bitmasks, counters, sizes, hash values, memory addresses.

Signed Integers: Two's Complement

Two's complement is the universal standard for signed integers. Every modern CPU uses it. The most significant bit (MSB) acts as the sign bit: 0 means positive (or zero), 1 means negative.

Bit Pattern (8-bit)Signed ValueWhy
000000000Zero is zero
000000011Standard binary
01111111127Largest positive (2^7 - 1)
10000000-128Smallest negative (-2^7)
11111110-2Invert: 00000001, +1 = 2 → -2
11111111-1Invert: 00000000, +1 = 1 → -1

To negate a number in two's complement: invert all bits and add 1. To find the value of a negative bit pattern: same process, same result.

// Negate 5 (00000101) in 8-bit two's complement:
// Invert: 11111010
// Add 1:  11111011  ← this is -5

// Decode 11111011 (what signed value is this?):
// MSB is 1 → negative
// Invert: 00000100
// Add 1:  00000101 → value is 5 → so pattern represents -5

Sign Extension: The Silent Bug Creator

When you assign a smaller signed type to a larger signed type, the compiler sign-extends: it copies the sign bit into all the new upper bits. This preserves the numeric value but changes the bit pattern.

int8_t  small = -1;      // 0xFF (11111111)
int16_t large  = small;   // 0xFFFF (sign-extended — still -1)
uint16_t ul    = small;   // 0xFFFF (same bits) but interpreted as 65535!

// This is the bug:
if (ul > 1000)
    printf("ul is huge: %u\n", ul);  // Prints 65535 — surprising!

Unsigned Wraparound vs Signed Overflow

Unsigned overflow is defined behavior in C — it wraps around modulo 2^N. Signed overflow is undefined behavior — the compiler can assume it never happens and optimize accordingly.

// Unsigned wraparound (defined — always works)
uint8_t u = 255;
u = u + 1;    // u becomes 0 (wraps around)

// Signed overflow (undefined behavior — nasal demons)
int8_t s = 127;
s = s + 1;    // UNDEFINED! Compiler can do anything

This is why for (unsigned i = n; i >= 0; i--) is an infinite loop — unsigned can never be negative. The condition i >= 0 is always true.

C Type Ranges at a Glance

C TypeBitsSigned RangeUnsigned Range
char / unsigned char8-128..1270..255
short / unsigned short16-32768..327670..65535
int / unsigned int32±2.1 billion0..4.3 billion
long long64±9.2×10^180..1.8×10^19

Rules of Thumb

Enter any number — see both signed and unsigned interpretation
Open Bitwise Calculator →