Integers and Arbitrary Precision

Updated

September 7, 2026

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.py

Output:

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.py

Pitfalls

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 conversion

Pitfall 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

  1. Write a function to_hex_and_bin(val: int) that returns a formatted string showing an integer in decimal, hexadecimal (0x...), and binary (0b...).
  2. Implement a function count_set_bits(n: int) -> int that counts how many binary 1s exist in an integer using bitwise operations (n & (n - 1)).
  3. 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.