Почему round(2.675, 2) в Python даёт неожиданный результат

Почему round(2.675, 2) в Python даёт неожиданный результат

@python_quiz

Разберем этот квиз

Коротко: выражение

python

не возвращает "магически" математически ожидаемое 2.68 из-за способа хранения чисел с плавающей запятой в двоичной форме. Разберёмся подробно и покажем, как получить "правильное" десятичное округление, если это нужно.

Что делает код на самом деле

Пример кода и ожидаемый результат:

python

Почему именно 2.67? Потому что представление 2.675 в двоичной плавающей точке чуть меньше истинного десятичного 2.675, то есть фактическое значение ближе к 2.67, и round округляет в эту сторону.

Демонстрация внутреннего представления

Наглядно покажем, как выглядят внутренние значения:

python

Типичный вывод (пример):

Как видно, значение float чуть меньше 2.675, поэтому round считает, что оно ближе к 2.67.

Округление «банковским» методом (round half to even)

Важно различать два источника путаницы:

  • Встроенная функция round в Python использует правило округления «к ближайшему, при равенстве — к чётному» (round half to even) для случаев, когда число точно равно середине между двумя соседними представимыми значениями.
  • В нашем случае число не является точной «половинкой» — оно уже немного меньше — поэтому поведение определяется не правилом «банковского» округления, а тем, к какому из соседних чисел float ближе представление.

Иными словами, наличие правила round-half-to-even не объясняет поведение для 2.675 — причина в двоичном представлении самого числа.

Анализ вариантов ответов (разбор смыслов)

  • "Выведет 2.67, так как округляет вниз"

Комментарий: результат действительно 2.67, но формулировка подразумевает, что round просто всегда округляет вниз — это не так. Причина конкретно в представлении числа, а не в общем «округлении вниз».

  • "Выведет 2.68, стандартное математическое округление"

Комментарий: кажется логичным с точки зрения десятичной арифметики, но из-за двоичного представления float стандартное ожидаемое поведение не проявляется.

  • "Выведет 2.68 из-за особого правила Banker's rounding"

Комментарий: Python использует округление «к чётному» для ровных половинок, но в данном случае значение не является точной половинкой — поэтому это объяснение неверно здесь.

  • "Выведет 2.67 из-за двоичного представления числа"

Комментарий: это точное объяснение: представление числа в формате IEEE-754 близко, но меньше 2.675, поэтому результат округления даёт 2.67.

(Здесь мы объяснили смысл каждого утверждения, чтобы понять, какие утверждения корректно описывают ситуацию.)

Как получить «ожидаемое» десятичное округление

Если вам нужно точное десятичное поведение (например, для финансовых расчётов), используйте модуль decimal:

python

Если вы хотите применить правило round-half-to-even (аналог поведения Python для ties) с Decimal:

python

Ключевой момент: создавайте Decimal из строк (Decimal('2.675')), а не из float (Decimal(2.675)), чтобы избежать унаследованной двоичной неточности.

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

  • Поведение round(2.675, 2) объясняется представлением числа в двоичной плавающей точке, а не «странным» багом самой функции round.
  • Правило «banker's rounding» (round half to even) применяется при точных половинках и иногда вызывает дополнительную путаницу, но не является причиной в этом конкретном примере.
  • Для строгого десятичного округления используйте decimal и передавайте туда строки (Decimal('...')).
  • При отладке неожиданных результатов округления полезно посмотреть на формат с высокой точностью (format(x, '.17f') или Decimal(x)) и float.hex().

Надеюсь, статья прояснила, почему простой пример с 2.675 ведёт себя непривычно, и показала, как правильно работать с десятичным округлением в Python.

Report Page