2025, Nov 11 00:02

Почему time.time_ns лучше float для меток времени в Python

Разбираем, почему float теряет субсекундную точность и как time.time_ns в Python дает целочисленные наносекундные метки времени удобные для BigInt и логирования.

Метки времени в Python на основе float удобны, но у них есть две острые грани, которые важны в продакшене. По мере роста значений точность снижается, а тип float неудобно укладывается в BigInt-колонки в базах данных. Если вам нужна долговечная, точная и удобная для СУБД форма представления времени, в стандартной библиотеке есть более удачное решение.

Проблема

Работа с float-меткой времени в секундах и последующее приведение к целому отбрасывают всю субсекундную детализацию. Это часто неприемлемо для логирования, упорядочивания событий или трассировки чувствительных к задержкам операций.

import datetime
# Секунды в формате float с начала Unix-эпохи
moment_f = datetime.datetime.now().timestamp()
# Целые секунды; дробная часть утрачена
moment_s = int(moment_f)

Хочется получить целочисленную метку времени без ручного приведения — прямой вызов, который возвращает целое со субсекундной точностью. Проблема не столько в самом касте, сколько в том, что представление float со временем теряет гранулярность.

Почему метки времени на float создают проблемы

Суть в том, как числа с плавающей запятой кодируют значения. По мере роста меток времени увеличивается шаг между представимыми значениями float, а значит, дробная часть теряет точность. Это не теоретическая мелочь — субсекундная точность реально ухудшается по мере удаления от эпохи.

Несколько практических наблюдений проясняют компромиссы:

При использовании чисел с плавающей запятой доступная точность уменьшается по мере движения в будущее — это, конечно, изъян. Вы получаете наносекундную точность лишь на несколько месяцев, теряя её примерно в 2am 6 апреля 1970 года. Микросекундную точность, напротив, можно сохранять до 2242 года. А если применять time.time_ns(), вы обходите эту проблему и получаете равномерную временную разрешающую способность базовой платформы.

Метка времени — это 64 битное число с плавающей запятой. Оно может точно представлять целые числа до 2**53.

Иными словами, float способен точно представить целые значения до 2**53, но как только вы опираетесь на дробную часть для субсекундной детализации, вы зависите от растущего шага между представимыми числами. Именно поэтому float — хрупкий выбор для высокоточного захвата времени на длинных отрезках.

Решение

Воспользуйтесь функцией стандартной библиотеки, которая сразу закрывает обе задачи — точность и целочисленный формат. Она возвращает количество наносекунд с начала Unix-эпохи в виде int.

import time
# Целые наносекунды с начала Unix-эпохи
epoch_ns_val = time.time_ns()

Так вы избегаете потери точности из‑за float и получаете значения времени, сразу пригодные для колонок BigInt в базе. Если вам нужны целые секунды, их можно получить из наносекунд, оставаясь в целочисленной арифметике.

import time
# Однократно зафиксировать время в целых наносекундах
now_ns_val = time.time_ns()
# Получить целые секунды без плавающей запятой
now_sec_i = now_ns_val // 1_000_000_000

Такой подход сохраняет равномерную субсекундную точность на момент измерения и позволяет понижать дискретность на ваших условиях.

Зачем это нужно

Во многих системах требуется надёжно упорядочивать события, связывать трассы или хранить метки времени между сервисами. По мере роста значений float теряет точность, поэтому он рискован как основа для долгоживущих данных. Целочисленные наносекунды сохраняют стабильность и переносимость данных. При этом не нужно жонглировать дополнительными структурами ради субсекундной точности.

Пожалуй, это «все остальные» делают не так, ведь потребность измерять время с субсекундной точностью встречается часто. Остальным приходится пользоваться громоздкими структурами.

В экосистемах, где функция преобразования — нормальная практика, удобнее иметь один канонический способ фиксации времени и конвертировать при необходимости. Это также хорошо сочетается с целочисленной моделью Python.

Для таких языков, как Python, использовать функцию преобразования — не проблема … мы просто адаптируем результат. Замечание: целые числа в Python — это не то, что привыкли видеть пользователи других языков. (они BigInt, то есть «бесконечная точность»)

Выводы и рекомендации

Если вы по привычке всё ещё используете float-метки времени, оцените последствия для точности и хранения. Для точных, долговечных и удобных для баз данных значений предпочитайте целочисленные наносекунды из стандартной библиотеки. Зафиксируйте время один раз как int через time.time_ns(), а затем переводите в нужную гранулярность — не возвращаясь к округлениям float.

Статья основана на вопросе на StackOverflow от Yadav Dhakal и ответе Mureinik.