Числа с плавающей точкой Python

Числа с плавающей точкой

Числа с плавающей точкой (англ. floating point numbers) - основной способ хранения дробных чисел в Python. Визуальное сходство с десятичными дробями часто сбивает с толку неискушенных в программировании новичков, вызывая досадные ошибки. Если бы Python использовал десятичные дроби, следующее выражение было бы истинным:

0.1 + 0.2 == 0.3
Но на деле это не так:

False

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

Что такое числа с плавающей точкой?

Числа с плавающей точкой имеют тип float. Их можно создавать непосредственно, или при делении целых чисел.

>>> a = 0.5
... b = 1 / 2
... print(type(a))
... print(type(b))
<class 'float'>
<class 'float'>

При создании переменной, дробь записывается в память в двоичной системе. Так, число 0.3 превращается в периодическую двоичную дробь 0.01(0011). То же самое происходит при записи одной трети в виде десятичной дроби: один фрагмент бесконечно повторяется (0.3333...). Поскольку память, отведенная на число конечная (и может отличаться на разных устройствах), дроби округляются и возникает небольшая погрешность. При обратном переводе в десятичную систему может обнаружиться, что число немного больше или меньше ожидаемого, как в нашем примере:

>>> 0.1 + 0.2
... format(0.3, ".17g")
0.30000000000000004
0.29999999999999999

Это мало сказывается на результатах вычислений, но, как мы уже убедились, может стать проблемой при сравнении двух чисел с плавающей точкой.

Числа с плавающей точкой можно округлить до определенного знака после точки, используя функцию round().

>>> round(0.1111111111111, 2)
0.11

Что следует помнить при использовании чисел с плавающей точкой?

Число будет округляться, если в нем больше значащих разрядов, чем позволяет разрядность системы. Очевидно, округляться будут все иррациональные числа (π, e...), а также десятичные дроби (0.1, 0.2…), бесконечные в двоичном представлении. Возникающая погрешность невелика, и в большинстве случаев проблема решается округлением.

>>> round(0.1 + 0.2, 4) == 0.3
True

Чтобы обойти проблему со строгим равенством, можно использовать функцию isclose() из модуля math:

>>> from math import isclose
... isclose(0.1 + 0.2, 0.3, rel_tol=1e-4)
True

Аргумент rel_tol - допускаемая функцией относительная погрешность. По умолчанию она равна 10-9. Также можно установить абсолютную погрешность abs_tol, для случаев, когда одно из чисел может быть равным нулю (в этом случае относительная погрешность неприменима).

Если вы работаете с массивами numpy, можете воспользоваться функцией isclose() для поэлементного сравнения или allclose() для проверки равенства массивов.

>>> import numpy as np
... a = np.array([[0.1, 0.2], [0.3, 0.4]], dtype=float)
... b = np.array([[0.01, 0.2], [0.3, 0.04]], dtype=float)
... print(np.isclose(a, b))
... print(np.allclose(a, b))
[[False  True]
 [ True False]]
False

Аргументы rtol и atol позволяют установить относительную и абсолютную погрешность, точно так же, как в аналогичной функции модуля math.

Другим подходом является использование типов Decimal из модуля decimal, позволяющего оперировать десятичными дробями, или Fraction из модуля fractions для работы с обыкновенными дробями.

Какие преимущества дают числа с плавающей точкой?

Очевидным преимуществом чисел с плавающей точкой является скорость операций с ними. Компьютер может непосредственно оперировать двоичным значением. Давайте сравним скорость трех упомянутых выше типов: float, Decimal и Fraction:

>>> from timeit import timeit
... from decimal import Decimal
... from fractions import Fraction
... a, b, c, d = Decimal(0.1), Decimal(0.2), Decimal(0.5), Decimal(0.7)
... e, f, g, h = Fraction(1, 10), Fraction(1, 5), Fraction(1, 2), Fraction(7, 10)
... print(timeit("(0.1 + 0.2) / 0.5 * 0.7", number=1000000, globals=globals()))
... print(timeit("(a + b) / c * d", number=1000000, globals=globals()))
... print(timeit("e + f / g * h", number=1000000, globals=globals()))
0.006274000000000002
0.3781116
3.8891881999999995

Разница в два порядка достаточна, чтобы перевесить все недостатки. В конце концов, как часто вам нужно знать число точнее четвертого знака после запятой?

Заключение

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

Практический Python для начинающих
Практический Python для начинающих

Станьте junior Python программистом за 7 месяцев

 7 месяцев

Возможно будет интересно

🏆 Hello, world! Python
Новичок
🏆 Hello, world!

Мы вчера запустили новый www.pylot.me. Должны были в следующую среду, но запустили вчера.

2022-10-04
Как практиковаться в Python? Python
Новичок
Как практиковаться в Python?

Для улучшения качества знаний и повышения уровня программиста, необходим постоянный практикум. Где можно это организовать самостоятельно, и как практиковаться в Python?

2022-10-19
Условные конструкции и сопоставление структурных шаблонов Шпаргалки
Новичок
Условные конструкции и сопоставление структурных шаблонов

Шпаргалка по условным конструкциям и сопоставлению структурных шаблонов

2022-11-09