Integers and Arbitrary Precision
Integers and Arbitrary Precision
After reading this chapter, you will master Python’s unbounded integer arithmetic, bitwise operations, numerical bases, and understand how CPython represents numbers without overflow.
Mental model
Unlike C, Java, or Go—where integers are fixed 32-bit or 64-bit hardware integers subject to overflow—Python’s int has arbitrary precision. It can store integers as large as your system memory permits.
CPython PyLongObject Structure (Heap):
┌───────────────────────────────────────────────────────────┐
│ ob_refcnt : 1 │ Reference count
│ ob_type : <class 'int'> │ Type pointer
│ ob_size : 3 (indicates 3 digits stored; sign) │ Size in digits
├───────────────────────────────────────────────────────────┤
│ ob_digit[0] : 0x3fffffff (low 30 bits) │
│ ob_digit[1] : 0x3fffffff (next 30 bits) │ Array of 30-bit
│ ob_digit[2] : 0x00000001 (high bits) │ base-2^30 digits
└───────────────────────────────────────────────────────────┘
CPython breaks large integers into chunks of 30 bits (on 64-bit systems) stored as an array of unsigned 32-bit digits. When numbers grow beyond one digit, CPython performs multi-precision schoolbook arithmetic automatically.
Minimal example
Save as arbitrary_ints.py:
# arbitrary_ints.py
def main() -> None:
# 256-bit cryptographic boundary
max_uint256 = (1 << 256) - 1
print(f"Max 256-bit integer:\n{max_uint256}")
# Computing 100! (factorial) without overflow
fact_100 = 1
for i in range(1, 101):
fact_100 *= i
print(f"\n100! has {len(str(fact_100))} decimal digits:")
print(f"{fact_100}")
if __name__ == "__main__":
main()Run via uv run python arbitrary_ints.py:
Max 256-bit integer:
115792089237316195423570985008687907853269984665640564039457584007913129639935
100! has 158 decimal digits:
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
Worked examples
Case 1: Bit manipulation and permission masks
Python supports the full suite of bitwise operators: & (AND), | (OR), ^ (XOR), ~ (NOT), and << / >> (shifts).
# bitmask_flags.py
# Standard UNIX file permission bits
READ = 0b100 # 4
WRITE = 0b010 # 2
EXECUTE = 0b001 # 1
def check_permissions(mask: int) -> None:
can_read = bool(mask & READ)
can_write = bool(mask & WRITE)
can_exec = bool(mask & EXECUTE)
print(f"Mask 0o{mask:o} ({bin(mask)}): r={can_read}, w={can_write}, x={can_exec}")
if __name__ == "__main__":
perm = READ | WRITE
check_permissions(perm)
# Add execute permission
perm |= EXECUTE
check_permissions(perm)
# Revoke write permission
perm &= ~WRITE
check_permissions(perm)Run:
uv run python bitmask_flags.pyOutput:
Mask 0o6 (0b110): r=True, w=True, x=False
Mask 0o7 (0b111): r=True, w=True, x=True
Mask 0o5 (0b101): r=True, w=False, x=True
Case 2: Integer division vs float division
In Python, / always performs true float division, while // performs floor division:
# division_rules.py
def main() -> None:
print(f"7 / 2 = {7 / 2} (always returns float)")
print(f"7 // 2 = {7 // 2} (floor division integer)")
print(f"-7 // 2 = {-7 // 2} (floored toward negative infinity!)")
print(f"7 % 2 = {7 % 2} (modulo remainder)")
# divmod returns (quotient, remainder) simultaneously
q, r = divmod(25, 4)
print(f"divmod(25, 4) -> quotient={q}, remainder={r}")
if __name__ == "__main__":
main()Run:
uv run python division_rules.pyPitfalls
Pitfall 1: Converting colossal integers to strings
Python enforces an integer string conversion security limit (4300 digits by default, configurable via sys.set_int_max_str_digits) to protect against quadratic-time denial of service attacks:
import sys
huge = 10**5000
# str(huge) -> ValueError: Exceeds the limit (4300 digits) for integer string conversionPitfall 2: Negative modulo differences from C
In C, -7 % 3 evaluates to -1. In Python, % always shares the sign of the divisor (denominator): -7 % 3 == 2 because -7 = (-3 * 3) + 2.
Exercises
- Write a function
to_hex_and_bin(val: int)that returns a formatted string showing an integer in decimal, hexadecimal (0x...), and binary (0b...). - Implement a function
count_set_bits(n: int) -> intthat counts how many binary1s exist in an integer using bitwise operations (n & (n - 1)). - Compute \(2^{1000}\) and print the number of bits required to represent it using the integer method
.bit_length().
Further reading
- CPython Implementation:
Objects/longobject.c. - Python Documentation:
sys.set_int_max_str_digits. - PEP 237: Unifying Long Integers and Integers.