Integer Representation¶
Computers represent integers using binary numbers stored in fixed-width memory. The exact representation depends on the programming environment.
Most programming languages and hardware systems use fixed-width two’s complement integers, while Python uses arbitrary-precision integers that grow dynamically as needed.
Understanding these representations is important for:
- preventing overflow bugs
- reasoning about memory usage
- understanding bitwise operations
- working with NumPy and low-level code
This chapter explains how integers are represented in hardware, how two’s complement works, and how Python’s integer implementation differs.
1. Unsigned Integers¶
The simplest integer representation is an unsigned integer.
An n-bit unsigned integer stores values using standard binary positional notation.
[ \text{value} = \sum_{i=0}^{n-1} b_i 2^i ]
where each (b_i) is either 0 or 1.
Range of unsigned integers¶
Because each bit has two possible states, an (n)-bit value has:
[ 2^n ]
possible patterns.
Therefore the range is:
[ 0 \text{ to } 2^n - 1 ]
Examples:
| Bits | Range |
|---|---|
| 8-bit | 0–255 |
| 16-bit | 0–65535 |
| 32-bit | 0–4,294,967,295 |
| 64-bit | 0–18,446,744,073,709,551,615 |
Example¶
Consider the 8-bit number:
11001010
[ 1\cdot128 + 1\cdot64 + 0\cdot32 + 0\cdot16 + 1\cdot8 + 0\cdot4 + 1\cdot2 + 0\cdot1 ]
[ = 202 ]
Visualization¶
flowchart TD
A["11001010"] --> B["1×128"]
A --> C["1×64"]
A --> D["0×32"]
A --> E["0×16"]
A --> F["1×8"]
A --> G["0×4"]
A --> H["1×2"]
A --> I["0×1"]
B --> J["202"]
C --> J
D --> J
E --> J
F --> J
G --> J
H --> J
I --> J
Unsigned integers cannot represent negative numbers.
To store signed values efficiently, computers use two’s complement representation.
2. Signed Integers and Two’s Complement¶
Modern hardware almost universally uses two’s complement to represent signed integers.
Two’s complement provides two important advantages:
- Addition hardware works for both signed and unsigned values
- Zero has only one representation
Range of two’s complement integers¶
For an (n)-bit signed integer:
[ -2^{n-1} \le x \le 2^{n-1}-1 ]
Example ranges:
| Bits | Range |
|---|---|
| 8-bit | -128 to 127 |
| 16-bit | -32768 to 32767 |
| 32-bit | -2,147,483,648 to 2,147,483,647 |
Bit weights in two’s complement¶
In an 8-bit signed integer, the leftmost bit has a negative weight.
| Bit | Value |
|---|---|
| (b_7) | −128 |
| (b_6) | 64 |
| (b_5) | 32 |
| (b_4) | 16 |
| (b_3) | 8 |
| (b_2) | 4 |
| (b_1) | 2 |
| (b_0) | 1 |
Example: interpreting a value¶
Consider:
11111011
Compute:
[ -128 + 64 + 32 + 16 + 8 + 0 + 2 + 1 ]
[ = -5 ]
So:
11111011 = -5
Visualization¶
flowchart TD
A["11111011"] --> B["-128"]
A --> C["+64"]
A --> D["+32"]
A --> E["+16"]
A --> F["+8"]
A --> G["+0"]
A --> H["+2"]
A --> I["+1"]
B --> J["-5"]
C --> J
D --> J
E --> J
F --> J
G --> J
H --> J
I --> J
3. Negating Numbers in Two’s Complement¶
To compute the negative of a number in fixed-width binary:
- invert all bits
- add 1
Example: represent −5 in 8 bits¶
Start with +5:
00000101
Invert bits:
11111010
Add 1:
11111011
Result:
-5 = 11111011
Visualization¶
flowchart TD
A["+5 = 00000101"] --> B["invert bits"]
B --> C["11111010"]
C --> D["+1"]
D --> E["11111011 = -5"]
4. Why Two’s Complement Works¶
Two’s complement enables subtraction to be performed using addition hardware.
Instead of computing:
[ a - b ]
the hardware computes:
[ a + (-b) ]
Because negation is easy (invert bits + add 1), subtraction becomes simple.
Example¶
Compute:
[ 7 - 3 ]
Binary form:
7 = 00000111
3 = 00000011
Compute −3:
11111101
Add:
00000111
+11111101
---------
00000100
Result:
4
The carry beyond the leftmost bit is discarded.
5. Overflow in Fixed-Width Integers¶
Hardware integers have fixed width.
If a computation produces a value outside the representable range, the result wraps around.
Mathematically, arithmetic occurs modulo (2^n).
Example: 8-bit overflow¶
Maximum signed value:
127 = 01111111
Add 1:
01111111
+00000001
---------
10000000
But:
10000000 = -128
So the value wraps.
Visualization¶
flowchart LR
A["127 = 01111111"] --> B["+1"]
B --> C["10000000"]
C --> D["interpreted as -128"]
Overflow occurs silently in many programming environments.
6. NumPy and Fixed-Width Integers¶
Libraries like NumPy use fixed-width integer types.
Examples include:
| dtype | size |
|---|---|
int8 |
1 byte |
int16 |
2 bytes |
int32 |
4 bytes |
int64 |
8 bytes |
Because these types follow hardware behavior, overflow wraps around silently.
Example: overflow in NumPy¶
import numpy as np
x = np.int8(127)
print(x + 1)
Output:
-128
The value wraps because the maximum 8-bit signed integer is 127.
7. Python Integers¶
Python integers behave differently.
Python uses arbitrary-precision integers, meaning the number of bits expands automatically when needed.
As a result:
- Python integers never overflow
- integer size grows dynamically
- operations may require more memory and time
Example:
print(2 ** 1000)
This produces a very large integer without overflow.
8. How Python Stores Integers¶
Internally, Python stores integers as:
- a sign
- a variable-length array of digits
Each digit stores 30 bits of the number.
Conceptually:
flowchart LR
A[Python int object] --> B[sign]
A --> C[array of base-2^30 chunks]
This design allows Python integers to grow without limit.
However, it also means Python integers consume more memory than fixed-width integers.
9. Memory Comparison¶
Small Python integers require around 28 bytes of memory.
Fixed-width integers are much smaller.
Example:
import sys
import numpy as np
print(sys.getsizeof(42))
print(np.int64(42).nbytes)
print(np.int8(42).nbytes)
Typical results:
| Type | Memory |
|---|---|
Python int |
~28 bytes |
np.int64 |
8 bytes |
np.int8 |
1 byte |
10. Bitwise Operations on Python Integers¶
Python simulates infinite-width two’s complement arithmetic for bitwise operations.
This means negative numbers behave as if they have infinitely many leading 1 bits.
Example:
print(bin(-5))
Output:
-0b101
But internally Python behaves as if:
...1111111111111111111111111011
Example: masking¶
-5 & 0xFF
Result:
251
Explanation:
11111011 (8-bit two's complement)
This is the unsigned representation of −5.
Visualization¶
flowchart LR
A["-5"] --> B["...1111111111111011"]
B --> C["mask with 0xFF"]
C --> D["11111011"]
D --> E["251"]
11. Choosing the Right Integer Type¶
Different situations require different integer representations.
Python integers¶
Advantages:
- no overflow
- simple arithmetic
- arbitrary precision
Disadvantages:
- larger memory usage
- slower for large arrays
NumPy integers¶
Advantages:
- compact
- fast vectorized operations
- predictable memory layout
Disadvantages:
- overflow wraps silently
- range must be chosen carefully
Example: incorrect dtype¶
import numpy as np
prices = np.array([30000, 35000, 40000], dtype=np.int16)
print(prices)
Output:
[30000 -30536 -25536]
The values exceed the int16 maximum of 32767.
Correct solution:
prices = np.array([30000, 35000, 40000], dtype=np.int32)
12. Worked Examples¶
Example 1¶
Interpret 11111110 as an 8-bit signed integer.
[ -128 + 64 + 32 + 16 + 8 + 4 + 2 + 0 = -2 ]
Example 2¶
Represent −12 in 8-bit two’s complement.
- Write 12:
00001100
- Invert:
11110011
- Add 1:
11110100
Example 3¶
Detect overflow.
120 + 20
In 8-bit signed arithmetic:
120 = 01111000
20 = 00010100
Sum:
10001100
This equals −116, showing overflow.
13. Exercises¶
- What is the range of a 12-bit unsigned integer?
- What is the range of a 12-bit two’s complement integer?
- Interpret
11110110as an 8-bit signed integer. - Represent −9 using 8-bit two’s complement.
- What happens when
np.int8(120) + np.int8(20)is computed? - Why does Python never overflow on integer arithmetic?
- How many bytes does an
np.int32use? - Why does two’s complement simplify hardware design?
14. Short Answers¶
- (0) to (4095)
- −2048 to 2047
- −10
11110111- Overflow occurs and the value wraps
- Python integers expand dynamically
- 4 bytes
- Addition hardware works for both positive and negative numbers
15. Summary¶
- Unsigned integers represent values from (0) to (2^n - 1).
- Two’s complement is the standard signed representation used in hardware.
- Signed integers range from (−2^{n−1}) to (2^{n−1} − 1).
- Negation is performed by inverting bits and adding 1.
- Fixed-width integers can overflow, wrapping around modulo (2^n).
- NumPy integers follow hardware behavior and may overflow silently.
- Python integers use arbitrary precision and never overflow, but require more memory.
Understanding integer representation is essential for reasoning about overflow, bitwise operations, memory efficiency, and numerical correctness in software systems.