Числа с плавающей точкой Python: float, double и точность вычислений
Основное решение: float в Python как аналог double
Как в Python работать с числами двойной точности (double)?
В языке Python нет отдельного типа double. Все числа с плавающей точкой имеют тип float, который соответствует 64-битному представлению IEEE 754 (двойная точность). Это означает, что любой литерал с десятичной точкой или операция, возвращающая дробное число, создаёт объект float - аналог double в C, Java или Fortran.
x = 3.14159
print(type(x)) # Float double python (тип double и float в python)
Проблема: в Python невозможно использовать 32-битный float (одинарная точность) стандартными средствами. Если требуется меньше точности или экономия памяти, приходится прибегать к сторонним библиотекам (например, numpy.float32). В большинстве задач двойная точность избыточна, но гарантирует высокую точность вычислений.
Как создать число с плавающей точкой и проверить его размер?
import sys
a = 1.0
print(sys.getsizeof(a)) # occupies 24 bytes (object overhead)
24
Объект float в Python занимает 24 байта из-за служебной информации, хотя сами данные (64 бита) - 8 байт. Это следует учитывать при работе с большими массивами.
Вариант 1: Явное указание числа с плавающей точкой через Decimal для финансовых расчётов
Как избежать погрешностей округления при работе с деньгами?
Тип float не подходит для денежных операций из-за ошибок представления. Модуль decimal позволяет задать точность и округление.
from decimal import Decimal, getcontext
getcontext().prec = 28 # задаём точность 28 знаков
price = Decimal('19.99')
tax = Decimal('0.07')
total = price * (1 + tax)
print(total) # 21.3893 (точное значение)
21.3893
Ошибка: передача в Decimal числа float (например, Decimal(19.99)) приводит к потере точности, так как сначала создаётся неточный float. Всегда используйте строковый литерал.
Вариант 2: Принудительное использование 32-битного float через NumPy
Как сократить потребление памяти в научных расчётах, где достаточно одинарной точности?
Библиотека NumPy поддерживает типы float32 (одинарная) и float64 (двойная). По умолчанию массивы создаются с float64.
import numpy as np
a32 = np.float32(1.0)
a64 = np.float64(1.0)
print(a32.dtype, a64.dtype) # float32 float64
float32 float64
Проблема: смешивание float32 и float64 в операциях может привести к автоматическому приведению к более широкому типу. Контролируйте типы явно.
Вариант 3: Дробные числа через fractions для точного представления рациональных чисел
Как избежать погрешностей при работе с дробями вида 1/3?
Модуль fractions хранит числа как пару (числитель, знаменатель). Результат всегда точен.
from fractions import Fraction
a = Fraction(1, 3)
b = Fraction(2, 5)
print(a + b) # 11/15
11/15
Недостаток: операции с Fraction медленнее, чем с float. Не подходит для больших массивов.
Вариант 4: Использование math.fsum для точного суммирования
Как сложить много чисел с плавающей точкой и минимизировать ошибку?
Функция math.fsum использует алгоритм Шевчука для точного суммирования.
import math
nums = [0.1] * 10
print(sum(nums)) # 0.9999999999999999
print(math.fsum(nums)) # 1.0
0.9999999999999999 1.0
Важно: fsum работает только с последовательностями чисел. Для итераторов результат тот же.
Расширенные примеры работы с float и double в Python
Пример 1: Сравнение чисел с плавающей точкой с допуском
Из-за ошибок представления прямое равенство float не рекомендуется.
a = 0.1 + 0.2
b = 0.3
print(a == b) # False
# Используем допуск (tolerance)
tolerance = 1e-10
print(abs(a - b) < tolerance) # True
False True
Пример 2: Экспоненциальная запись и преобразование строк
num = 1.23e-4
print(num) # 0.000123
print(f"{num:.2e}") # 1.23e-04
# Из строки
s = "2.5e3"
print(float(s)) # 2500.0
0.000123 1.23e-04 2500.0
Пример 3: Максимальное и минимальное значение float
import sys
print(sys.float_info.max) # 1.7976931348623157e+308
print(sys.float_info.min) # 2.2250738585072014e-308
print(sys.float_info.epsilon) # 2.220446049250313e-16 (машинный эпсилон)
1.7976931348623157e+308 2.2250738585072014e-308 2.220446049250313e-16
Пример 4: NaN и inf
import math
a = float('inf')
b = float('-inf')
c = float('nan')
print(math.isinf(a)) # True
print(math.isnan(c)) # True
# Операции с NaN
print(c + 1) # nan
True True nan
Пример 5: Преобразование массива numpy из float64 в float32 и обратно
import numpy as np
arr = np.array([1.23456789, 2.34567891], dtype=np.float64)
arr32 = arr.astype(np.float32)
print(arr32) # [1.2345679 2.3456788] (потеря точности)
# Обратно в float64 – потерянные знаки не восстанавливаются
arr_back = arr32.astype(np.float64)
print(arr_back) # [1.2345678806304932, 2.3456788063049316]
[1.2345679 2.3456788] [1.23456788 2.34567881]
Пример 6: Использование decimal.Decimal с контекстом для округления
from decimal import Decimal, ROUND_HALF_UP, getcontext
getcontext().prec = 4
getcontext().rounding = ROUND_HALF_UP
value = Decimal('2.675')
rounded = value.quantize(Decimal('0.01'))
print(rounded) # 2.68 (а не 2.67, как было бы с float)
2.68
Пример 7: Получение битового представления float (через struct)
import struct
def float_to_bits(f):
return bin(struct.unpack('!I', struct.pack('!f', f))[0])
# Для double (Python float)
def double_to_bits(d):
return hex(struct.unpack('!Q', struct.pack('!d', d))[0])
print(float_to_bits(1.0)) # 0b0111111110000000000000000000000 (32 бита)
print(double_to_bits(1.0)) # 0x3ff0000000000000
0b111111110000000000000000000000 0x3ff0000000000000