2025, Nov 25 18:02

Почему «произвольная точность» целых в Python не бесконечна

Разбираем реальные пределы целых чисел в Python: память против произвольной точности, где возникают MemoryError и OverflowError, как измерить потолок.

Целые числа в Python 3 — произвольной точности, но это не значит «бесконечные». Практический потолок по‑прежнему определяется ресурсами и ограничениями реализации. Распространённая ошибка — приравнивать объём оперативной памяти к максимальному значению целого, которое можно напрямую сохранить. Несоответствие между битами и байтами, а также разница между «числом битов» и «числовым значением» быстро рушат такую интуицию.

Где обычно ошибаются

Исходная посылка нередко звучит так: возьмём 64‑битную систему с N гигабайтами ОЗУ, например 32 ГБ, и предположим, что наибольшая степень двойки, которую можно представить, привязана к этому объёму памяти. Уже здесь арифметика даёт сбой. Тридцать два гигабайта — это не 32 · 2^30 бит, а 32 · 8 · 2^30, то есть 2^38 бит. Это ёмкость в битах, а не значение какого‑то целого числа.

Имея b бит, можно кодировать значения от 0 до 2^b − 1. Если бы можно было отдать все 2^38 бит исключительно под двоичные разряды одного целого, верхняя граница для этого числа составила бы 2^(2^38) − 1. Это астрономически большое число и одновременно чисто теоретическое: вы упрётесь в практические ограничения задолго до того, как задействуете каждый бит ОЗУ под один Python int.

Минимальный код, который выявляет проблему

Проще всего увидеть, где всё разваливается, если попробовать конструировать целые, сдвигая 1 влево на огромное число позиций. Уже одна строка показывает, что происходит при экстремальных величинах:

probe_value = 1 << (1 << 66)

Это выражение пытается создать целое с колоссальным числом битов. На реальной машине вы далеко не уйдёте — Python или ОС быстро начнут жаловаться.

Как обстоят дела на самом деле

Путаница возникает из‑за смешения единицы объёма хранения с диапазоном значений, который этот объём может представить. Пример с 32 ГБ даёт 2^38 бит памяти, но 2^38 — это количество битов, а не числовое значение. 8‑битная величина достигает максимум 255; по той же логике, пул из 2^38 бит, если бы его можно было использовать как одно сплошное целое, упирался бы в 2^(2^38) − 1. Есть и сторона реализации. На практике крупные выделения памяти падают раньше из‑за давления на память, и существует дополнительная граница, при которой Python поднимает исключение для целых, превышающих внутренний порог размера.

Эмпирические проверки хорошо это иллюстрируют. При построении целых вида 1 << (1 << p) значения при p до 39 включительно получались; попытки с p от 40 до 65 приводили к MemoryError; а при p равном 66 и выше возникало OverflowError: too many digits in integer. Этот рисунок показывает два разных режима отказа: сначала исчерпание памяти, а затем жёсткий лимит для ещё более крупных целей. Отмечают также, что, по‑видимому, существует граница вида 7.5 * (2**63 - 1), но практически вы почти наверняка столкнётесь с MemoryError задолго до такого порога.

Практичный способ проверить на своей машине

Когда теории мало, проверьте границы контролируемо. Идея проста: пробовать строить целые с быстро растущей длиной в битах и отслеживать исключения. Вот компактный драйвер, который делает ровно это:

def try_bigints(max_pow: int) -> None:
    for p in range(max_pow + 1):
        try:
            giant = 1 << (1 << p)
            print(f"power={p}: OK")
        except MemoryError:
            print(f"power={p}: MemoryError")
            break
        except OverflowError as err:
            print(f"power={p}: OverflowError: {err}")
            break
# Example invocation
try_bigints(70)

Этот шаблон отражает наблюдавшиеся результаты: успех при малых p, MemoryError в среднем диапазоне и OverflowError дальше. Конкретные значения p, на которых проявится каждое поведение, зависят от вашей среды.

Почему это важно

«Произвольная точность» — это гарантия семантики, а не бесконечный ресурс. Python будет наращивать целое по мере необходимости, но рост ограничен памятью и защитными барьерами реализации. В реальных задачах вы столкнётесь с MemoryError раньше, чем с каким‑либо теоретическим максимумом, подсчитанным по битам вашей ОЗУ. И даже не исчерпав память, в какой‑то момент Python остановится с явным OverflowError для чрезмерно больших целых.

Выводы

Не оценивайте наибольшее представимое целое в Python, трактуя объём системной памяти как прямой максимум значения. Биты и байты описывают вместимость; сами значения растут экспоненциально с числом битов. Если нужен практический потолок, измерьте его. Запустите небольшой пробный скрипт и посмотрите, где у вас возникает MemoryError, и помните, что после определённой точки возможен OverflowError: too many digits in integer. Если встретите ссылку на верхнюю границу вида 7.5 * (2**63 - 1), помните: на практике память закончится намного раньше.