Числа с плавающей точкой
Числа с плавающей точкой (англ. 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
, но стоит учитывать, что математические операции с ними работают гораздо медленнее.
Возможно будет интересно
🏆 Hello, world!
Мы вчера запустили новый www.pylot.me. Должны были в следующую среду, но запустили вчера.
Как практиковаться в Python?
Для улучшения качества знаний и повышения уровня программиста, необходим постоянный практикум. Где можно это организовать самостоятельно, и как практиковаться в Python?
Условные конструкции и сопоставление структурных шаблонов
Шпаргалка по условным конструкциям и сопоставлению структурных шаблонов