Data representation: binary, hexadecimal, two's complement and characters
Binary and hexadecimal, two's complement, overflow in C with examples, fixed-width types, fixed point, ASCII and UTF-8, bitwise operators.
Inside a computer everything is made of bits: numbers, letters, images, instructions. The same group of 8 bits, 01000001, can be the number 65, the letter A or part of an instruction, depending on how it is interpreted. In this lesson we see how signed and unsigned integers are represented, two’s complement, hexadecimal notation, characters and the overflow problems that follow.
The binary system
In base 2 each digit is worth a power of 2:
. To convert a decimal number to binary, divide repeatedly by 2 and read the remainders from the bottom up: 13 → 6 remainder 1, 3 remainder 0, 1 remainder 1, 0 remainder 1, so
.
With
bits you represent
values: with 8 bits (a byte) from 0 to 255, with 16 bits from 0 to 65,535, with 32 bits from 0 to about 4.3 billion.
Hexadecimal
Binary numbers are long and hard to read. Base 16 uses the digits 0-9 and A-F (A = 10, …, F = 15), and each hexadecimal digit corresponds to exactly 4 bits. Conversion is immediate, in groups of four:
| Binary | 1010 | 0011 | 1111 | 0000 |
|---|---|---|---|---|
| Hexadecimal | A | 3 | F | 0 |
So
. In C hexadecimal numbers are written with the prefix 0x. Memory addresses, web page colours (#FF8000) and register values in datasheets are written in hexadecimal.
Signed numbers: two’s complement
The natural way to represent negatives, a sign bit plus the magnitude, has two flaws: two zeros (+0 and −0) and different adder circuits for positives and negatives. Every processor uses two’s complement instead: with
bits, the most significant bit weighs
instead of
.

With 4 bits:
. The ranges are
to
: with 8 bits −128 to 127, with 32 bits about −2.1 to +2.1 billion.
How to change sign. Invert all the bits and add 1. From
: invert, 1010; add 1, 1011 = −5. Check:
, and the carry beyond the fourth bit is discarded: 0.
The advantage. Addition works the same way for positives and negatives: the same adder computes
by adding
, that is 4 discarding the carry. Subtraction becomes addition of the opposite.
Overflow
If the result does not fit in the available bits, it “wraps round” the wheel. With 8-bit signed numbers,
. In C:
#include <stdio.h>
#include <stdint.h>
int main(void)
{
uint8_t u = 250;
u = u + 10; /* unsigned: 260 - 256 = 4 */
printf("%u\n", u); /* prints 4 */
int16_t t = 30000;
t = t + 10000; /* signed: the result does not fit in 16 bits */
printf("%d\n", t); /* on almost every system prints -25536 */
return 0;
}
For unsigned types C guarantees arithmetic modulo
. For signed ones overflow is undefined behaviour: the compiler may assume it never happens, and results can be surprising. It is one of the most common sources of firmware bugs: a 32-bit unsigned millisecond counter wraps to zero after about 49.7 days, and code comparing times must allow for it.
The fixed-width types of <stdint.h> (uint8_t, int16_t, uint32_t, …) are preferred in microcontroller programs, because the size of int changes from one processor to another: 16 bits on an AVR, 32 on an ARM.
Real numbers
Numbers with a fractional part are represented in floating point according to IEEE 754 (the float and double types in C), described in the Numerical Analysis lesson on errors. On microcontrollers without a floating-point unit fixed point is often used instead: you work with integers that represent, for example, thousandths (a voltage of 3.301 V becomes 3301 mV).
Characters
A character is a number according to an encoding table. ASCII (1963) uses 7 bits for 128 characters: ‘A’ = 65, ‘a’ = 97, ‘0’ = 48, space = 32, newline = 10. In C a char is a small integer, and you can do arithmetic on characters: '7' - '0' is 7, the standard way to turn a digit into a number.
For all the world’s scripts there is Unicode, with over 150,000 characters, almost always encoded in UTF-8: ASCII characters take one byte, accented letters two (è = 0xC3 0xA8), ideograms three, emoji four. That is why a C string holding “café” takes 5 bytes, not 4.
Bitwise operators
C lets you work on individual bits with the operators & (AND), | (OR), ^ (XOR), ~ (NOT), << and >> (shifts). A left shift by
positions multiplies by
: x << 3 equals x * 8. They are essential for communication protocols and for peripheral registers, as we will see in the lesson on C for embedded systems.





