Binary and Hexadecimal¶
Mental Model
Binary is the computer's native tongue -- every value is a string of 0s and 1s. Hexadecimal is a shorthand that humans invented to read binary without going cross-eyed: each hex digit maps to exactly four bits, so a byte like 11010010 becomes D2. Whenever you see hex in debuggers, memory dumps, or color codes, you are looking at binary in disguise.
Computers store and manipulate data using binary numbers—numbers written in base 2. While binary is ideal for hardware, it is often inconvenient for humans to read or write long sequences of bits.
To make binary data easier to work with, programmers frequently use hexadecimal (base 16) and sometimes octal (base 8). These number systems provide compact representations of the same underlying bit patterns.
Understanding how to convert between these bases is an essential skill when working with:
- debugging tools and memory dumps
- binary file formats
- network protocols
- cryptographic data
- low-level systems programming
1. Number Systems and Bases¶
A number system (or base) defines how numbers are represented using digits and positional value.
Each digit position represents a power of the base.
For a base (b):
[ \text{value} = \sum d_i b^i ]
where (d_i) are the digits.
Common bases used in computing¶
| Base | Name | Digits |
|---|---|---|
| 2 | Binary | 0–1 |
| 8 | Octal | 0–7 |
| 10 | Decimal | 0–9 |
| 16 | Hexadecimal | 0–9, A–F |
Hexadecimal extends decimal with six additional symbols:
| Hex | Value |
|---|---|
| A | 10 |
| B | 11 |
| C | 12 |
| D | 13 |
| E | 14 |
| F | 15 |
Visualization of positional value¶
flowchart LR
A["Digit × base^position"] --> B["Digit × 16^3"]
A --> C["Digit × 16^2"]
A --> D["Digit × 16^1"]
A --> E["Digit × 16^0"]
Each position represents a power of the base.
2. Binary Numbers¶
Binary uses only two digits:
text
0 and 1
Each position represents a power of two.
| Position | Value |
|---|---|
| (2^0) | 1 |
| (2^1) | 2 |
| (2^2) | 4 |
| (2^3) | 8 |
| (2^4) | 16 |
| (2^5) | 32 |
| (2^6) | 64 |
| (2^7) | 128 |
Example: binary to decimal¶
Convert:
text
101101
[ 1\cdot2^5 + 0\cdot2^4 + 1\cdot2^3 + 1\cdot2^2 + 0\cdot2^1 + 1\cdot2^0 ]
[ = 32 + 8 + 4 + 1 = 45 ]
Visualization¶
flowchart TD
A["101101"] --> B["1×32"]
A --> C["0×16"]
A --> D["1×8"]
A --> E["1×4"]
A --> F["0×2"]
A --> G["1×1"]
B --> H["45"]
C --> H
D --> H
E --> H
F --> H
G --> H
3. Decimal to Binary Conversion¶
To convert a decimal number to binary, repeatedly divide by 2 and record the remainders.
Example: convert 45 to binary¶
| Division | Quotient | Remainder |
|---|---|---|
| 45 ÷ 2 | 22 | 1 |
| 22 ÷ 2 | 11 | 0 |
| 11 ÷ 2 | 5 | 1 |
| 5 ÷ 2 | 2 | 1 |
| 2 ÷ 2 | 1 | 0 |
| 1 ÷ 2 | 0 | 1 |
Reading remainders bottom to top:
text
101101
Visualization¶
flowchart TD
A["45"] --> B["22 r1"]
B --> C["11 r0"]
C --> D["5 r1"]
D --> E["2 r1"]
E --> F["1 r0"]
F --> G["0 r1"]
G --> H["Binary: 101101"]
4. Hexadecimal¶
Binary numbers become long quickly. For example:
text
1010111100111100
To make these easier to read, programmers use hexadecimal.
Hexadecimal uses base 16, with digits:
text
0 1 2 3 4 5 6 7 8 9 A B C D E F
Why hexadecimal is useful¶
Hexadecimal aligns perfectly with binary.
text
1 hex digit = 4 bits
So a byte (8 bits) equals:
text
2 hex digits
Binary to hex grouping¶
Binary:
text
1010 1111 0011 1100
Group into 4 bits:
text
1010 1111 0011 1100
Convert each group:
| Binary | Hex |
|---|---|
| 1010 | A |
| 1111 | F |
| 0011 | 3 |
| 1100 | C |
Result:
text
AF3C
Visualization¶
flowchart LR
A["1010111100111100"] --> B["1010"]
A --> C["1111"]
A --> D["0011"]
A --> E["1100"]
B --> F["A"]
C --> G["F"]
D --> H["3"]
E --> I["C"]
F --> J["AF3C"]
G --> J
H --> J
I --> J
5. Hexadecimal to Decimal¶
To convert hex to decimal, multiply each digit by the corresponding power of 16.
Example:
text
0xAF
[ A\times16^1 + F\times16^0 ]
[ 10\times16 + 15 = 175 ]
Larger example¶
text
0x3C7
[ 3\times16^2 + 12\times16^1 + 7\times16^0 ]
[ = 768 + 192 + 7 = 967 ]
6. Octal¶
Octal (base 8) uses digits:
text
0–7
Octal was historically useful because:
text
1 octal digit = 3 bits
Example:
text
101 110 111
Convert groups:
| Binary | Octal |
|---|---|
| 101 | 5 |
| 110 | 6 |
| 111 | 7 |
Result:
text
567
Today, octal is mainly used in Unix permissions.
Example:
text
755
means:
text
rwxr-xr-x
7. Binary, Hex, and Bytes¶
Because hexadecimal aligns perfectly with binary, it is widely used when inspecting raw data.
Example byte:
text
11001010
Group bits:
text
1100 1010
Convert:
text
CA
So:
text
11001010 = 0xCA
Visualization¶
flowchart LR
A["11001010"] --> B["1100"]
A --> C["1010"]
B --> D["C"]
C --> E["A"]
D --> F["0xCA"]
E --> F
8. Python and Number Bases¶
Python provides convenient syntax for working with different bases.
Literals¶
Python uses prefixes to specify number bases.
| Prefix | Base |
|---|---|
0b |
binary |
0x |
hexadecimal |
0o |
octal |
Example:
python
print(0b101101) # 45
print(0xFF) # 255
print(0o755) # 493
Converting integers to strings¶
python
print(bin(45)) # '0b101101'
print(hex(255)) # '0xff'
print(oct(493)) # '0o755'
Parsing numbers from strings¶
python
print(int('101101', 2)) # 45
print(int('FF', 16)) # 255
9. Practical Uses of Hexadecimal¶
Hexadecimal appears frequently in programming because it represents binary data compactly.
Common uses include:
- memory addresses
- binary file formats
- color values
- machine instructions
- cryptographic hashes
Example: RGB colors¶
Many graphics systems represent colors using hexadecimal.
Example:
```text
3498DB¶
```
Structure:
text
RR GG BB
Each pair represents a byte.
Extracting color components¶
```python color = 0x3498DB
r = (color >> 16) & 0xFF g = (color >> 8) & 0xFF b = color & 0xFF
print(r, g, b) ```
Result:
text
52 152 219
Visualization¶
flowchart LR
A["0x3498DB"] --> B["34"]
A --> C["98"]
A --> D["DB"]
B --> E["Red"]
C --> F["Green"]
D --> G["Blue"]
10. Fixed-Width Formatting¶
Programs often display numbers using a fixed number of digits.
Example:
python
format(45, '08b')
Output:
text
00101101
This shows the number as 8-bit binary.
Hex formatting¶
python
format(255, '02x')
Output:
text
ff
11. Bytes and Hex Strings¶
Binary data is frequently represented as hexadecimal strings.
Example:
python
data = bytes([0xDE, 0xAD, 0xBE, 0xEF])
This produces:
text
deadbeef
which is a well-known hexadecimal example used in debugging.
Example:
python
print(data.hex())
print(bytes.fromhex('deadbeef'))
12. Worked Examples¶
Example 1¶
Convert 11011010 to hex.
Group bits:
text
1101 1010
Convert:
text
D A
Result:
text
0xDA
Example 2¶
Convert 0x7B to decimal.
[ 7\times16 + 11 = 123 ]
Example 3¶
Convert 73 to binary.
Break into powers of two:
[ 64 + 8 + 1 ]
Binary:
text
01001001
13. Exercises¶
- Convert
10101101to decimal. - Convert 91 to binary.
- Convert
11100110to hexadecimal. - Convert
0x2Fto decimal. - Convert
0xA3to binary. - Convert
755(octal) to decimal. - Why does hexadecimal align so well with binary?
- What hex value corresponds to
11111111?
Exercise 9. The positional value formula says that a number in base \(b\) is \(\sum d_i \cdot b^i\). Using this formula, explain why every hex digit maps to exactly four binary digits, every octal digit maps to exactly three binary digits, but there is no clean grouping of binary digits that maps to one decimal digit. What mathematical property must the base have for such a clean mapping to exist?
Solution to Exercise 9
A clean digit-to-digit mapping between base \(b_1\) and base \(b_2\) exists if and only if \(b_1\) is a power of \(b_2\) (or vice versa). Hexadecimal is \(16 = 2^4\), so each hex digit corresponds to exactly \(4\) binary digits. Octal is \(8 = 2^3\), so each octal digit corresponds to exactly \(3\) binary digits.
Decimal (\(10\)) is not a power of \(2\). There is no integer \(k\) such that \(10 = 2^k\). This means there is no fixed number of binary digits that maps cleanly to one decimal digit. Converting between binary and decimal requires actual arithmetic (multiplication, addition, division), not just grouping.
This is why hexadecimal became the preferred shorthand for binary data: the conversion is purely mechanical grouping with no arithmetic required.
Exercise 10. Consider the following Python session:
python
x = 0xFF
y = int('FF', 16)
z = 255
print(x == y == z) # What does this print?
print(type(x), type(y), type(z)) # What types are these?
Predict the output. Then explain: when Python encounters 0xFF in source code, does it store the number internally in hexadecimal? What is the relationship between a number's value and its representation in a particular base?
Solution to Exercise 10
The output is:
text
True
<class 'int'> <class 'int'> <class 'int'>
All three variables hold the same integer value 255 and have the same type int. Python does not store numbers in hexadecimal, decimal, or any particular base internally. The prefixes 0x, 0b, 0o are source code notations -- they tell the parser how to interpret the literal, but the resulting integer object is base-independent. Internally, CPython stores the value as an array of binary chunks (base \(2^{30}\) digits), but this is an implementation detail.
A number's value is abstract and independent of base. A representation is how we write that value using a specific base and digit symbols. 0xFF, 255, 0b11111111, and 0o377 are four different representations of the same value.
Exercise 11.
A programmer writes format(255, '04x') and gets '00ff'. They then try format(-1, '04x') expecting 'ff' (thinking of 8-bit two's complement), but instead get '-1'. Explain why Python's format() with 'x' does not produce two's complement hex for negative numbers. How does this relate to the fact that Python integers have no fixed bit width? What would the programmer need to do to get the 8-bit two's complement hex representation of -1?
Solution to Exercise 11
Python's format() with 'x' treats the integer as a mathematical value, not as a fixed-width bit pattern. Since Python integers have arbitrary precision (no fixed bit width), there is no "natural" two's complement representation for negative numbers. The format specifier simply prepends a minus sign for negative values.
In a language like C, -1 as an int8 is 0xFF because int8 has exactly 8 bits. But Python's -1 could be 8-bit, 16-bit, 32-bit, or any width -- there is no single "correct" hex representation.
To get the 8-bit two's complement hex:
python
value = -1
twos_comp = value & 0xFF # Mask to 8 bits: gives 255
print(format(twos_comp, '02x')) # 'ff'
The & 0xFF operation forces the value into an 8-bit unsigned range, effectively computing the two's complement representation at that width.
Exercise 12.
RGB color values are often written as 6-digit hex strings like #3498DB. Explain why hexadecimal is the natural choice for this rather than decimal or binary. Then consider: if humans could comfortably read base-256 (with 256 distinct digit symbols), how would the same color be written? Why don't we use base-256 in practice, and what does this tell us about why hexadecimal became the standard "human-friendly binary shorthand"?
Solution to Exercise 12
Hexadecimal is natural for RGB colors because each color channel is one byte (8 bits = values 0--255), and one byte is exactly two hex digits. So #3498DB immediately reveals three channels: 34 (red = 52), 98 (green = 152), DB (blue = 219). The representation is compact (6 characters) and each channel boundary aligns with a digit boundary.
In decimal, the same color would be 52, 152, 219 -- three separate numbers requiring delimiters, with no fixed width. In binary, it would be 00110100 10011000 11011011 -- 24 characters, hard to read.
In base-256, each channel would be a single "digit," so the color would be just 3 digits. This would be maximally compact, but humans cannot memorize 256 distinct symbols. Hexadecimal (16 symbols: 0--9, A--F) is the sweet spot: it is a power of 2 (so binary conversion is trivial), each digit represents exactly 4 bits, and 16 symbols are few enough for humans to learn easily. This balance between compactness and human readability is why hexadecimal became the standard shorthand.
14. Short Answers¶
- 173
01011011E6- 47
10100011- 493
- Because one hex digit equals four bits
FF
15. Summary¶
- Binary (base 2) is the fundamental number system used by computers.
- Hexadecimal (base 16) provides a compact representation of binary data.
- Octal (base 8) groups bits in sets of three and appears in Unix permissions.
- One hex digit represents exactly four bits.
- One byte corresponds to two hex digits.
- Programmers frequently convert between bases when debugging, inspecting memory, or working with binary protocols.
Understanding binary and hexadecimal allows programmers to reason about bit patterns, memory layout, and low-level data formats with precision.
Exercises¶
Exercise 1. Convert the decimal number 42 to binary and hexadecimal.
Solution to Exercise 1
Binary: 42 = 32 + 8 + 2 = 101010 in binary. Hexadecimal: 42 = 2 * 16 + 10 = 0x2A.
Exercise 2. Why is hexadecimal commonly used in computing instead of decimal.
Solution to Exercise 2
Each hexadecimal digit corresponds to exactly 4 binary digits (bits), making conversion between hex and binary trivial. A byte (8 bits) is always exactly 2 hex digits. This makes hexadecimal a compact, human-readable representation of binary data that preserves bit-level structure.