Числа с плавающей точкой: тип float
Введение
Тип float в Python используется для представления вещественных чисел с плавающей точкой. Однако из-за двоичного представления многие десятичные дроби не могут быть точно сохранены, что приводит к ошибкам округления. Здесь описаны основные подходы к работе с float, их преимущества и недостатки.
Основное решение: точные вычисления с Decimal
Для критичных к точности вычислений (финансовые операции, научные расчёты) рекомендуется использовать модуль decimal. Он позволяет задавать точность и тип округления, избегая ошибок двоичного представления.
from decimal import Decimal, getcontext
# Установка точности
getcontext().prec = 28
# Создание Decimal из строки
a = Decimal('0.1')
b = Decimal('0.2')
c = a + b
print(c) # 0.3 точночисла float python (числа с плавающей точкой (float) в python)
0.3
тип с плавающей точкой python (тип с плавающей точкой (float) в python)
Проблемы: Decimal работает медленнее float и требует явного преобразования. При создании из float теряется точность: Decimal(0.1) даст неточное значение. Следует использовать строки.
Ошибка: Decimal(0.1) != Decimal('0.1'). Первый вариант приводит к сохранению неточного float. Рекомендуется всегда передавать строку или целое число.
Как округлить float до определённого количества знаков?
Функция round() округляет число до заданного числа знаков после запятой. Однако при округлении .5 может возникнуть "банковское округление" (round half to even).
print(round(2.675, 2)) # Ожидается 2.68, но из-за двоичного представления может быть 2.67
2.67
Для предсказуемого округления рекомендуется Decimal.quantize().
Проблема: round(2.5) даёт 2, а не 3 из-за банковского округления. Это неожиданно для новичков.
Как правильно сравнивать два float числа на равенство?
Прямое сравнение == может дать ложный результат из-за погрешностей. Следует применять math.isclose() с допустимой относительной или абсолютной погрешностью.
import math
a = 0.1 + 0.2
b = 0.3
print(a == b) # False
print(math.isclose(a, b, rel_tol=1e-9)) # True
False True
Ошибка: использование abs(a-b) < 1e-9 может не работать для очень малых или очень больших чисел. isclose учитывает масштаб.
Как вывести float с заданным числом знаков после запятой?
Форматирование строк через f-строки или метод format() позволяет контролировать отображение без изменения значения.
value = 1.234567
print(f"{value:.2f}") # 1.23
print("{:.3f}".format(value)) # 1.235
1.23 1.235
Проблема: форматирование не изменяет само число, только его строковое представление. При последующих вычислениях используется исходное значение.
Как работать с рациональными числами без потери точности?
Модуль fractions.Fraction представляет числа как дробь (числитель/знаменатель), сохраняя точность.
from fractions import Fraction
f = Fraction(1, 3) + Fraction(2, 3) # 3/3 = 1
print(f) # 1
1
Подходит для точных вычислений, но не для иррациональных чисел (pi, sqrt).
Ошибка: преобразование float в Fraction может привести к огромным знаменателям из-за двоичного представления. Можно использовать Fraction.from_float(0.1).limit_denominator().
Расширенные примеры работы с float
1. Научная нотация и большие числа
# Экспоненциальная запись
large = 1.5e10 # 15000000000.0
small = 2.5e-7 # 0.00000025
print(large, small)
print(large + small) # 15000000000.0 (small теряется из-за ограниченной точности)
15000000000.0 2.5e-07 15000000000.0
2. Преобразование типов и потеря точности
# int -> float
i = 10
f = float(i) # 10.0
# float -> int (отбрасывание дробной части)
print(int(3.9)) # 3
# Длинная строка в float
s = "3.141592653589793238"
f = float(s) # 3.141592653589793 (ограниченная точность)
3 3.141592653589793
3. Использование Decimal для финансовых расчётов
from decimal import Decimal, ROUND_HALF_UP
price = Decimal('19.99')
quantity = Decimal('3')
total = price * quantity
print(total) # 59.97
# Округление до копеек
rounded = total.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
print(rounded) # 59.97
59.97 59.97
4. Сравнение float с помощью math.isclose() и abs
import math
# Разные tol
a = 0.1 + 0.2
b = 0.3
print(math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)) # True
print(math.isclose(1e10, 1e10+1, rel_tol=1e-9)) # False (разница 1, rel_tol=0.01)
# Для нуля
print(math.isclose(0.0, 1e-20, abs_tol=1e-15)) # True
True False True
5. Бесконечность и NaN
import math
# Бесконечность
pos_inf = float('inf')
neg_inf = float('-inf')
print(pos_inf > 1e308) # True
# NaN
nan = float('nan')
print(nan == nan) # False
print(math.isnan(nan)) # True
print(math.isfinite(pos_inf)) # False
True False True False
6. Битовое представление float (sys.float_info)
import sys
info = sys.float_info
print("Мантисса (бит):", info.mant_dig)
print("Экспонента (бит):", info.max_exp)
print("Минимальное положительное:", sys.float_info.min)
print("Максимальное:", sys.float_info.max)
# 1.0 + epsilon
eps = sys.float_info.epsilon
print(1.0 + eps == 1.0) # False (эпсилон - наименьшая разница)
Мантисса (бит): 53 Экспонента (бит): 1024 Минимальное положительное: 2.2250738585072014e-308 Максимальное: 1.7976931348623157e+308 False
7. Работа с fractions для точных дробей
from fractions import Fraction
# Создание из float с ограничением знаменателя
f = Fraction.from_float(0.1).limit_denominator(10)
print(f) # 1/10
# Арифметика
a = Fraction(1, 3)
b = Fraction(2, 5)
c = a + b
print(c) # 11/15
print(float(c)) # 0.7333333333333333
1/10 11/15 0.7333333333333333
8. Форматирование с учётом региональных стандартов
import locale
locale.setlocale(locale.LC_ALL, 'ru_RU.UTF-8') # может потребоваться поддержка
value = 1234567.89
print(locale.format_string("%.2f", value)) # 1 234 567,89 (зависит от локали)
1 234 567,89