Числа с плавающей точкой в Python: точность, округление и Decimal/Fraction на практике
Запросы вроде «точность float в Python», «почему 0.1 + 0.2 != 0.3», «округление денег в Python» встречаются у каждого, кто переходит от «игрушечных» примеров к реальным задачам. В этой статье вы получите практическое руководство по числам с плавающей точкой: как избежать ловушек, когда использовать Decimal или Fraction, и какие инструменты помогают писать надёжный код.
Почему 0.1 + 0.2 != 0.3 в Python
Тип float — это двоичная (IEEE 754) аппроксимация десятичных чисел. Не все десятичные дроби можно представить в двоичной системе точно, поэтому возникает небольшая погрешность.
print(0.1 + 0.2) # 0.30000000000000004
print((0.1 + 0.2) == 0.3) # False
# Для «красивого» вывода используйте форматирование
x = 0.1 + 0.2
print(format(x, '.17f')) # 0.30000000000000004
print(f"{x:.2f}") # 0.30 (лишь формат вывода, не исправление значения)
Важно понимать: форматированный вывод не меняет реального значения, а только отображение. Для корректных сравнений и вычислений нужны правильные инструменты.
Безопасные сравнения: math.isclose
Сравнивать float на полное равенство — анти‑паттерн. Используйте допуски через math.isclose.
import math
x = 0.1 + 0.2
print(math.isclose(x, 0.3)) # True по умолчанию
# Настраиваемые допуски
print(math.isclose(x, 0.3, rel_tol=1e-09, abs_tol=0.0))
# Для значений около нуля учитывайте abs_tol
print(math.isclose(1e-12, 0.0, abs_tol=1e-10)) # True
Рекомендации: для инженерных расчётов задавайте явные rel_tol и abs_tol, для денег переходите на Decimal.
Округление в Python: round и его особенности
Функция round(x, ndigits) применяет «банковское» округление (Banker’s Rounding, половины к чётному). Это может удивить при .5.
print(round(2.5)) # 2 (к чётному)
print(round(3.5)) # 4
print(round(1.25, 1)) # 1.2
print(round(1.35, 1)) # 1.4
Если вам нужно классическое «.5 вверх», используйте Decimal с режимом ROUND_HALF_UP и фиксируйте масштаб с помощью quantize.
Decimal: точные десятичные вычисления для финансов
decimal.Decimal хранит числа в десятичной системе без двоичных артефактов. Главные правила: создавайте Decimal из строк (или целых), настраивайте контекст и явно задавайте масштаб при деньгах.
from decimal import Decimal, getcontext, ROUND_HALF_UP, localcontext
# Никогда не создавайте Decimal из float: Decimal(0.1) наследует погрешность float!
price = Decimal('0.10')
qty = Decimal('3')
subtotal = price * qty # 0.30 точно
print(subtotal) # 0.30
# Настройка контекста (общая точность)
getcontext().prec = 28
# Корректное округление денег до 2 знаков «.5 вверх»
amount = Decimal('10.015')
final = amount.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
print(final) # 10.02
# Локальная настройка контекста (например, временное повышение точности)
with localcontext() as ctx:
ctx.prec = 40
print((Decimal('1') / Decimal('7')).quantize(Decimal('0.0000000000000000001')))
Практика: храните деньги в базе как Decimal(precision, scale) или как целые копейки (int), а при показе делите и форматируйте. Для финансовых операций не используйте float.
Fraction: точные рациональные дроби
fractions.Fraction представляет число как рациональную дробь p/q без потерь точности. Полезно в задачах, где требуется строгая рациональная арифметика (комбинаторика, музыкальные интервалы, точные преобразования единиц).
from fractions import Fraction
print(Fraction(1, 3) + Fraction(1, 6)) # 1/2
# Из float — не рекомендуется (унаследуете двоичную погрешность)
print(Fraction(0.1)) # 3602879701896397/36028797018963968
# Правильно: из строки или из десятичной дроби с известным знаменателем
print(Fraction('0.1')) # 1/10
print(Fraction(1, 10)) # 1/10
# Приближение дробью с ограничением знаменателя (полезно для измерений)
x = 3.14159
print(Fraction(x).limit_denominator(1000)) # 355/113
Fraction идеально точен, но может раздуваться по размеру числителя/знаменателя и быть медленнее. Выбирайте осознанно.
Суммирование и минимизация ошибок: fsum
Даже при float можно уменьшить накопление ошибок благодаря math.fsum, который суммирует с повышенной точностью.
import math
vals = [1e16, 1, -1e16]
print(sum(vals)) # 0.0 (потеря единицы)
print(math.fsum(vals)) # 1.0 (корректнее)
Практические мини‑кейсы
1) Денежные расчёты: скидка, НДС, итоги
from decimal import Decimal, ROUND_HALF_UP
price = Decimal('1999.90')
discount = Decimal('0.15') # 15%
vat = Decimal('0.20') # 20%
# Цена со скидкой (два знака после запятой)
price_disc = (price * (Decimal('1') - discount)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
# НДС на итоговую цену
vat_amount = (price_disc * vat).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
# Итог к оплате
total = (price_disc + vat_amount).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
print(price_disc, vat_amount, total)
Здесь мы явно фиксируем масштаб (2 знака) и режим округления «.5 вверх», как это обычно требуется в отчётности и бухгалтерии.
2) Инженерия: сравнение измерений с допусками
import math
expected = 10.0
measured = 9.9994
# Допуски: 0.01 по абсолютной ошибке или 1e-6 по относительной
print(math.isclose(measured, expected, abs_tol=0.01)) # True
print(math.isclose(measured, expected, rel_tol=1e-6)) # False
Подбирайте допуски согласно требованиям предметной области, фиксируйте их в константах и пишите тесты на граничные случаи.
Когда float, когда Decimal, когда Fraction
- float — графика, статистика, научные расчёты, где допустимы микропогрешности; используйте
math.isclose,math.fsum, форматирование при выводе. - Decimal — финансы, отчётность, любые операции с фиксированным количеством знаков; создавайте из строк, применяйте
quantizeи нужный режим округления. - Fraction — точная рациональная математика, преобразования единиц, где важна идеальная точность дробей; следите за производительностью и ростом знаменателей.
Частые ошибки и как их избежать
- Не сравнивайте float на равенство — используйте
math.iscloseс явными допусками. - Не создавайте Decimal из float: используйте строки или целые числа.
- Для денег задавайте масштаб через
quantize(Decimal('0.01'))и подходящий режим округления (ROUND_HALF_UPи др.). - Для суммирования больших списков float применяйте
math.fsum. - Если нужна математическая точность дробей — используйте
Fractionи при необходимостиlimit_denominator.
Дальше по теме
Хотите быстро собрать прочную базу по Python с практикой, проектами и домашними заданиями? Посмотрите программу курса «Программирование на Python с Нуля до Гуру» — отличный путь от основ к уверенной разработке: Посмотреть программу и стартовать сегодня.
Итог: точность float в Python — не баг, а особенность представления чисел. Зная, когда применять float, Decimal или Fraction, вы избежите проблем с округлением, получите корректные отчёты и предсказуемые вычисления.
-
Создано 01.04.2026 17:01:10
-
Михаил Русаков

Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.