Counting in binary and hex
Binary and hex are the same positional counting you use for decimal, with a different base. How to read a byte as a number, and why hex exists.
On this page
Binary and hex are not new maths. They are the counting you already do, with a different base.
This lesson shows the rule once, then applies it three times: decimal, binary, hex. After it you can read a byte as a number and read hex in a debugger or a memory dump without guessing.
How does positional counting work? How do we read a byte? Why is hex used?
Decimal is positional
Take the number 305.
3 0 5
3×100 0×10 5×1 = 300 + 0 + 5 = 305
Each position has a weight. Moving left, the weight is multiplied by 10: 1, 10, 100, 1000. A digit says how many of that weight you have. The 10 is the base: how many different digits exist before you have to carry into the next position.
A car odometer does exactly this. A wheel shows 0 to 9. When it passes 9, it goes back to 0 and advances the wheel next to it by one. That carry is the entire mechanism.
Binary is the same, with base 2
Binary has two digits, so it carries after 1 instead of after 9. Moving left, the weight is multiplied by 2: 1, 2, 4, 8, 16, and so on.
Here is counting from 0 to 15, in decimal, binary and hex:
dec bin hex dec bin hex
0 0000 0 8 1000 8
1 0001 1 9 1001 9
2 0010 2 10 1010 a
3 0011 3 11 1011 b
4 0100 4 12 1100 c
5 0101 5 13 1101 d
6 0110 6 14 1110 e
7 0111 7 15 1111 f
Look at the rightmost bit: it flips on every step. The bit next to it flips every second step, the next every fourth. Every position flips half as often as the one on its right. That is the odometer, with two-digit wheels.
Reading a byte
A byte has eight positions. The weights, right to left, are 1, 2, 4, 8, 16, 32, 64, 128. To read it, add the weights of the positions that hold a 1.
Always start from the right. The rightmost position has weight 1.
To go the other way, from a number to bits, walk the weights from the biggest down and ask "does it fit?":
72
128? no (too big) 0
64? yes 72 − 64 = 8 1
32? no 0
16? no 0
8? yes 8 − 8 = 0 1
4? no 0
2? no 0
1? no 0
72 = 0100 1000
Every position on gives 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = 255. A byte therefore holds 0 to 255. In general, n bits hold 0 to 2ⁿ − 1.
Why hex exists
Binary is exact, but it is hard to read. Try spotting the difference between 1111 1111 0000 0000 and 1111 1110 0000 0000 at a glance.
Hex is base 16: sixteen digits. Decimal digits only go to 9, so hex borrows letters:
a = 10 b = 11 c = 12 d = 13 e = 14 f = 15
The reason it works so well is that 16 = 2⁴. Four bits have exactly 16 patterns, so one hex digit corresponds to exactly four bits. Converting between binary and hex needs no arithmetic. You look up each group of four bits in the table above.
Another example, 1111 1111 0000 0000: 1111 is f, 0000 is 0, so it is 0xff00.
The 0x prefix only says "this number is written in hex", so 0x48 is not mistaken for forty-eight.
Hex to decimal
The same weight rule applies, with base 16. The left digit is multiplied by 16.
0x48 = 4×16 + 8 = 64 + 8 = 72
0x2a = 2×16 + 10 = 32 + 10 = 42
0xff = 15×16 + 15 = 240 + 15 = 255
0xff is the largest byte, the same 255 as eight 1s in binary.
Where you will meet hex
- Colours.
#ff0000is three bytes: redff(maximum), green00, blue00. Pure red. - Memory addresses. Debuggers and crash reports print addresses in hex.
- Raw bytes. Tools like
xxd, packet captures and file inspectors show data as hex, two digits per byte.
Binary is what the machine stores. Hex is the compact way a human reads it.
Recap
- Each position has a weight: decimal ×10, binary ×2, hex ×16.
- Read a byte by adding the weights of its 1s. A byte holds 0 to 255.
- 16 = 2⁴, so one hex digit is exactly four bits and a byte is two hex digits.
0xmarks a number as hex.
Try it
Check your own arithmetic:
python3 -c "print(bin(72), hex(72), int('0x48', 16))"
Expected: 0b1001000 0x48 72. Python drops the leading zero, so 0b1001000 is the same 0100 1000.
Before you run it, predict what hex(255) and hex(42) print.