Python: уроки, справочник и задачи

Округление чисел в Python: от round() до Decimal

Раздел: Основы Python · Обновлено 25.10.2026
Финансовый калькулятор HP-12C с числами на дисплее
Фото: Pittigrilli, CC0

Округление в Python кажется простой задачей ровно до первого сюрприза: round(2.5) возвращает 2, а round(2.675, 2) дает 2.67 вместо ожидаемых 2.68. Ни то, ни другое не баг. Первое - сознательно выбранное банковское округление, второе - следствие того, как хранятся числа с плавающей точкой.

Ниже разобраны все способы округления: функция round, функции модуля math, форматирование при выводе и модуль decimal для денег. Для каждого показано, когда его применять и где он подводит.

Почему 0.1 + 0.2 не равно 0.3

Прежде чем округлять, нужно понять, что именно мы округляем. Тип float хранит число в двоичной системе по стандарту IEEE 754. Многие привычные десятичные дроби, например 0.1, в двоичной системе бесконечны, как 1/3 в десятичной. Компьютер хранит ближайшее представимое значение.

print(0.1 + 0.2)            # -> 0.30000000000000004
print(0.1 + 0.2 == 0.3)     # -> False

from decimal import Decimal
print(Decimal(0.1))
# -> 0.1000000000000000055511151231257827021181583404541015625

Decimal(0.1) показывает точное значение, которое на самом деле лежит в переменной. Python при печати выводит самую короткую строку, которая однозначно задает это значение, поэтому обычно мы видим аккуратное 0.1. Ошибки накапливаются при арифметике и становятся видны.

Отсюда первое практическое правило: никогда не сравнивайте float через ==. Используйте math.isclose:

import math
print(math.isclose(0.1 + 0.2, 0.3))   # -> True

Округление через round(x, 2) == 0.3 тоже иногда работает, но это костыль. Подробнее об устройстве вещественных чисел рассказано в статье float в Python.

Функция round и банковское округление

Встроенная функция round(number, ndigits=None) округляет до заданного числа знаков после запятой. Без второго аргумента она возвращает int, с ним - тот же тип, что и у входного числа.

print(round(3.14159))       # -> 3      (int)
print(round(3.14159, 2))    # -> 3.14   (float)
print(round(3.7, 0))        # -> 4.0    (float, а не int)
print(round(1234.5678, -2)) # -> 1200.0 (отрицательные знаки - до сотен)

Отрицательный ndigits округляет до десятков, сотен, тысяч. Это удобно для отчетов: round(87_450, -3) дает 87000.

Почему round(2.5) равно 2

В школе учат: пятерка округляется вверх. Python 3 поступает иначе: если число ровно посередине между двумя кандидатами, выбирается четный. Это называется банковским округлением, или округлением половины к четному (round half to even).

print([round(x) for x in (0.5, 1.5, 2.5, 3.5, 4.5, -2.5)])
# -> [0, 2, 2, 4, 4, -2]

Зачем так сделано? При школьном правиле все "половинки" сдвигаются в одну сторону, и при суммировании большого количества округленных чисел накапливается систематическая ошибка. При округлении к четному половинки уходят то вверх, то вниз, и в среднем ошибка компенсируется. Такой режим предписывает стандарт IEEE 754, его же используют в бухгалтерии и статистике.

Почему round(2.675, 2) дает 2.67

Здесь банковское правило ни при чем. Число 2.675 нельзя точно записать в float, и реально хранится значение чуть меньше:

print(round(2.675, 2))   # -> 2.67
print(Decimal(2.675))
# -> 2.67499999999999982236431605997495353221893310546875

Это не половинка, а число меньше половинки, поэтому оно честно округляется вниз. round работает правильно, просто на вход пришло не то число, которое вы написали в коде. Если такой результат недопустим, нужен Decimal, о нем ниже.

Округление вверх и вниз: math.ceil, floor и trunc

Модуль math дает три функции, которые округляют до целого по направлению, а не до ближайшего:

import math

for x in (2.3, 2.7, -2.3, -2.7):
    print(x, math.floor(x), math.ceil(x), math.trunc(x), int(x), round(x))
# ->  2.3  2  3  2  2  2
# ->  2.7  2  3  2  2  3
# -> -2.3 -3 -2 -2 -2 -2
# -> -2.7 -3 -2 -2 -2 -3

Для положительных чисел floor и trunc совпадают, а для отрицательных расходятся: floor(-2.3) равно -3, а trunc(-2.3) равно -2. Функция int() ведет себя как trunc. Все три функции возвращают int. Остальные функции модуля описаны в статье модуль math.

Сводная таблица способов округления

СпособПравило2.5-2.5Тип результата
round(x)к ближайшему, половина к четному2-2int
math.floor(x)вниз2-3int
math.ceil(x)вверх3-2int
math.trunc(x), int(x)к нулю2-2int
Decimal + ROUND_HALF_UPк ближайшему, половина от нуля3-3Decimal
f"{x:.0f}"к ближайшему, половина к четному"2""-2"str

Округление вверх при делении целых

Частая задача: сколько страниц нужно для 101 записи, если на странице 20? Нужен потолок от деления. Можно написать math.ceil(101 / 20), но для больших целых надежнее остаться в целых числах и не связываться с float:

items, per_page = 101, 20
print(math.ceil(items / per_page))   # -> 6
print(-(-items // per_page))         # -> 6, только целочисленная арифметика
print((items + per_page - 1) // per_page)   # -> 6

Оператор // всегда округляет вниз, к минус бесконечности: -7 // 2 равно -4, а не -3. Трюк с двойным минусом превращает пол в потолок.

Округление до 2 знаков после запятой

Самый частый запрос - округлить до двух знаков. Тут важно различать две задачи: изменить само число или только показать его красиво.

Форматирование при выводе: f"{x:.2f}"

Если число нужно только напечатать, округлять его не надо. Используйте спецификатор формата: значение в переменной останется точным, а на экран выйдет нужное количество знаков.

price = 1234.5
ratio = 0.1 + 0.2

print(f"{price:.2f}")       # -> 1234.50  (дополнит нулями)
print(f"{ratio:.2f}")       # -> 0.30
print(f"{price:,.2f}")      # -> 1,234.50
print(f"{price:_.2f}".replace("_", " "))   # -> 1 234.50
print(f"{0.4567:.1%}")      # -> 45.7%

В отличие от round, форматирование всегда выводит ровно указанное число знаков: round(1234.5, 2) напечатается как 1234.5, а f"{1234.5:.2f}" как 1234.50. Для отчетов и интерфейсов почти всегда нужен второй вариант. Правило округления у форматирования то же, что у round: f"{2.675:.2f}" даст 2.67. Все возможности спецификаторов разобраны в статье f-строки в Python.

Округление вверх до двух знаков и ловушка умножения

Стандартный рецепт округления вверх до сотых - умножить, взять потолок, разделить. Но он ломается на float:

x = 1.1
print(x * 100)                    # -> 110.00000000000001
print(math.ceil(x * 100) / 100)   # -> 1.11  (ожидали 1.1)

Умножение добавило к числу крошечный хвост, и потолок отправил его на следующую сотую. Для таких задач правильный инструмент - Decimal с режимом ROUND_CEILING.

Модуль decimal: точное десятичное округление

Decimal хранит числа в десятичной системе, поэтому 0.1 в нем - это ровно 0.1. Ключевое правило: создавайте Decimal из строки, а не из float. Из float вы точно скопируете уже испорченное значение.

from decimal import Decimal, ROUND_HALF_UP, ROUND_CEILING, ROUND_FLOOR

print(Decimal("0.1") + Decimal("0.2"))   # -> 0.3
print(Decimal(2.675))     # -> 2.67499999999999982236431605997495353221893310546875
print(Decimal("2.675"))   # -> 2.675

Метод quantize и ROUND_HALF_UP

Округление в decimal делается методом quantize. Ему передают образец с нужным количеством знаков и режим округления:

CENT = Decimal("0.01")

def round_money(value: str | Decimal) -> Decimal:
    return Decimal(value).quantize(CENT, rounding=ROUND_HALF_UP)

print(round_money("2.675"))   # -> 2.68
print(round_money("2.665"))   # -> 2.67
print(round_money("-2.675"))  # -> -2.68
print(Decimal("2.5").quantize(Decimal("1"), rounding=ROUND_HALF_UP))   # -> 3
print(Decimal("1.1").quantize(CENT, rounding=ROUND_CEILING))           # -> 1.10
print(Decimal("1.101").quantize(CENT, rounding=ROUND_CEILING))         # -> 1.11

ROUND_HALF_UP - это то самое школьное округление: половина уходит от нуля, поэтому -2.675 становится -2.68. Если на входе float, сначала превратите его в строку: Decimal(str(2.675)) даст Decimal('2.675'), потому что str() возвращает короткое представление.

Основные режимы округления decimal

РежимОписание2.5-2.52.1
ROUND_HALF_UPшкольное, половина от нуля3-32
ROUND_HALF_EVENбанковское (режим по умолчанию)2-22
ROUND_HALF_DOWNполовина к нулю2-22
ROUND_CEILINGвверх, к плюс бесконечности3-23
ROUND_FLOORвниз, к минус бесконечности2-32
ROUND_UPот нуля3-33
ROUND_DOWNк нулю (отбросить хвост)2-22

Обратите внимание на названия: ROUND_UP означает "от нуля", а не "вверх". Для округления вверх в математическом смысле нужен ROUND_CEILING.

Округление в расчетах с деньгами

С деньгами float использовать нельзя: ошибки в последних разрядах рано или поздно превратятся в расхождение на копейку в отчете. Есть два надежных подхода.

  1. Целые копейки. Храните суммы в int в минимальных единицах валюты. Сложение и вычитание точные, а форматирование для вывода делается в одном месте.
  2. Decimal. Удобен, когда есть проценты, налоги и курсы с дробными ставками. Округляйте через quantize в явно оговоренных точках расчета, а не после каждой операции.

Классическая задача - разделить сумму на части так, чтобы ничего не потерялось. 100 рублей на троих не делятся поровну, и простое округление каждой доли теряет копейку:

def split_amount(total_kop: int, parts: int) -> list[int]:
    base, rest = divmod(total_kop, parts)
    return [base + 1 if i < rest else base for i in range(parts)]

shares = split_amount(10_000, 3)
print(shares)        # -> [3334, 3333, 3333]
print(sum(shares))   # -> 10000
print([f"{s // 100}.{s % 100:02d}" for s in shares])
# -> ['33.34', '33.33', '33.33']

Функция divmod дает целую часть и остаток, а остаток раздается по одной копейке первым долям. Сумма всегда сходится. Пример расчета НДС на Decimal:

price = Decimal("1999.99")
vat_rate = Decimal("0.20")
vat = (price * vat_rate / (1 + vat_rate)).quantize(CENT, rounding=ROUND_HALF_UP)
print(vat)           # -> 333.33
print(price - vat)   # -> 1666.66

Подробнее о типах int, float и complex рассказано в статье числа в Python 3.

Типичные ошибки при округлении

  1. Округление на каждом шаге. Если округлять промежуточные результаты, ошибки складываются. Считайте с полной точностью и округляйте только итог или значения, которые по правилам должны фиксироваться (например, сумма строки в чеке).
  2. Decimal из float. Decimal(0.1) переносит ошибку float в точный тип. Всегда передавайте строку или целое число.
  3. Смешивание Decimal и float. Выражение Decimal("1.5") * 1.1 выбросит TypeError. Это защита: Python не дает незаметно испортить точные вычисления. Приводите все операнды к Decimal.
  4. Округление ради сравнения. round(a, 2) == round(b, 2) может дать разный результат для почти равных чисел по разные стороны от границы округления. Для сравнения используйте math.isclose(a, b, abs_tol=0.005).
  5. Ожидание нулей в конце от round. round(2.50, 2) печатается как 2.5. Нули в конце - задача форматирования, а не округления.

Отдельно о модуле statistics и функциях вроде sum: при сложении множества float ошибки накапливаются. Для точной суммы вещественных чисел есть math.fsum, которая отслеживает потерянные разряды: sum([0.1] * 10) дает 0.9999999999999999, а math.fsum([0.1] * 10) - ровно 1.0.

Как выбрать способ округления

Частые вопросы

Как в Python сделать обычное математическое округление, где 2.5 дает 3?

Используйте Decimal: Decimal("2.5").quantize(Decimal("1"), rounding=ROUND_HALF_UP). Для float можно написать int(Decimal(str(x)).quantize(Decimal("1"), rounding=ROUND_HALF_UP)). Самодельный вариант math.floor(x + 0.5) работает для положительных чисел, но неверно обрабатывает отрицательные половинки.

Почему round(x) возвращает int, а round(x, 0) - float?

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

Как вывести 5 как 5.00?

Через форматирование: f"{5:.2f}" или format(5, ".2f"). Функция round нули в конце не добавляет, потому что у числа нет "количества знаков", это свойство строкового представления.

Как отбросить знаки после запятой без округления?

До целого - math.trunc(x) или int(x). До двух знаков надежно - Decimal(str(x)).quantize(Decimal("0.01"), rounding=ROUND_DOWN). Вариант math.trunc(x * 100) / 100 страдает от той же ошибки умножения, что и пример с 1.1.

Округляет ли numpy так же, как round?

Да, numpy.round и numpy.around тоже используют округление половины к четному. Для школьного правила в массивах обычно применяют numpy.floor(x + 0.5) с теми же оговорками про отрицательные числа и точность float.