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

Язык Python предоставляет три типа значений с плавающей точкой: встроенные типы float и complex и тип decimal.Decimal в стандартной библиотеке. Все три типа данных относятся к категории неизменяемых. Тип float представляет числа с плавающей точкой двойной точности, диапазон значений которых зависит от компилятора языка C (или C\# или Java), применявшегося для компиляции интерпретатора Python. Числа этого типа имеют ограниченную точность и не могут надежно сравниваться на равенство значений. Числа типа float записываются с десятичной точкой или в экспоненциальной форме записи, например, 0.0, 4., 5.7, -2.5, -2e9, 8.9e-4.

В машинном представлении числа с плавающей точкой хранятся как двоичные числа. Это означает, что одни дробные значения могут быть представлены точно (такие как 0.5), а другие – только приблизительно (такие как 0.1 и 0.2). Кроме того, для представления используется фиксированное число битов, поэтому существует ограничение на количество цифр в представлении таких чисел. Ниже приводится поясняющий пример:

>>> 0.0, 5.4, -2.5, 8.9e-4

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

Если действительно необходимо обеспечить высокую точность, можно использовать числа типа decimal.Decimal. Эти числа обеспечивают уровень точности, который вы укажете (по умолчанию 28 знаков после запятой), и могут точно представлять периодические числа, такие как \( 0.1 \) , но скорость работы с такими числами существенно ниже, чем с обычными числами типа float. Вследствие высокой точности числа типа decimal.Decimal прекрасно подходят для производства финансовых вычислений.

Смешанная арифметика поддерживается таким образом, что результатом выражения с участием чисел типов int и float является число типа float, а с участием типов float и complex результатом является число типа complex. Поскольку числа типа decimal.Decimal имеют фиксированную точность, они могут участвовать в выражениях только с другими числами decimal.Decimal и с числами типа int; результатом таких выражений является число decimal.Decimal. В случае попытки выполнить операцию над несовместимыми типами возбуждается исключение TypeError.

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

Все числовые операторы и функции, представленные в табл. 1, могут применяться к числам типа float, включая комбинированные операторы присваивания. Тип данных float может вызываться как функция – без аргументов возвращается число 0.0, с аргументом типа float возвращается копия аргумента, а с аргументом любого другого типа предпринимается попытка выполнить преобразование указанного объекта в тип float. При преобразовании строки аргумент может содержать либо простую форму записи числа с десятичной точкой, либо экспоненциальное представление числа. При выполнении операций с числами типа float может возникнуть ситуация, когда в результате получается значение NaN (not a number – не число) или «бесконечность». К сожалению, поведение интерпретатора в таких ситуациях может отличаться в разных реализациях и зависит от математической библиотеки системы.

Ниже приводится пример простой функции, выполняющей сравнение чисел типа float на равенство в пределах машинной точности:

def equal_float(a, b):
    return abs(a - b) <= sys.float_info.epsilon

Чтобы воспользоваться этой функцией, необходимо импортировать модуль sys. Объект sys.float_info имеет множество атрибутов. Так, sys.float_info.epsilon хранит минимально возможную разницу между двумя числами с плавающей точкой. На одной из 32-разрядных машин автора книги это число чуть больше \( 0.000 000 000 000 000 2 \). Тип float в языке Python обеспечивает надежную точность до 17 значащих цифр.

В дополнение к встроенным функциональным возможностям работы с числами типа float модуль math предоставляет множество функций, которые приводятся в табл. datatype:tbl:4. Ниже приводятся несколько фрагментов программного кода, демонстрирующих, как можно использовать функциональные возможности модуля:

>>> import math
>>> math.pi * (5 ** 2)
78.539816339744831
>>> math.hypot(5, 12)
13.0
>>> math.modf(13.732)
(0.73199999999999932, 13.0)

Модуль math в значительной степени опирается на математическую библиотеку, с которой был собран интерпретатор Python. Это означает, что при некоторых условиях и в граничных случаях функции модуля могут иметь различное поведение на различных платформах.

Таблица 4. Функции и константы модуля math

Синтаксис Описание
math.acos(x) Возвращает арккосинус x в радианах
math.acosh(x) Возвращает гиперболический арккосинус x в радианах
math.asin(x) Возвращает арксинус x в радианах
math.asinh(x) Возвращает гиперболический арксинус x в радианах
math.atan(x) Возвращает арктангенс x в радианах
math.atan2(y x) Возвращает арктангенс y/x в радианах
math.atanh(x) Возвращает гиперболический арктангенс x в радианах
math.ceil(x) Возвращает $ | x | $, то есть наименьшее целое число типа int, большее и равное x, например, math.ceil(5.4) == 6
math.copysign(x y) Возвращает x со знаком числа y
math.cos(x) Возвращает косинус x в радианах
math.cosh(x) Возвращает гиперболический косинус x в радианах
math.degrees(r) Преобразует число r типа float из радианов в градусы
math.e Константа \( e \), примерно равная значению \( 2.7182818284590451 \)
math.exp(x) Возвращает \( e^x \), то есть math.e ** x
math.fabs(x) Возвращает $ | x | $, то есть абсолютное значение x в виде числа типа float
math.factorial(x) Возвращает \( x! \)
math.floor(x) Возвращает $ | x | $, то есть наименьшее целое число типа int, меньшее и равное x, например, math.floor(5.4) == 5
math.fmod(x y) Выполняет деление по модулю (возвращает остаток) числа x на число y; дает более точный результат, чем оператор %, применительно к числам типа float
math.frexp(x) Возвращает кортеж из двух элементов с мантиссой (в виде числа типа float) и экспонентой (в виде числа типа int)
math.fsum(i) Возвращает сумму значений в итерируемом объекте i в виде числа типа float
math.hypot(x y) Возвращает \( \sqrt{x^2 + y^2} \)
math.isinf(x) Возвращает True, если значение x типа float является бесконечностью (\( \pm \infty \))
math.isnan(x) Возвращает True, если значение x типа float не является числом
math.ldexp(m e) Возвращает \( m\times 2^e \) – операция обратная math.frexp()
math.log(x b) Возвращает \( \log_b x \), аргумент b является необязательным и по умолчанию имеет значение math.e
math.log10(x) Возвращает \( log_{10} x \)
math.log1p(x) Возвращает \( log_e (1+x) \); дает точные значения даже когда значение x близко к 0
math.modf(x) Возвращает дробную и целую часть числа x в виде двух значений типа float
math.pi Константа \( \pi \), примерно равная \( 3.1415926535897931 \)
math.pow(x y) Возвращает \( x^y \) в виде числа типа float
math.radians(d) Преобразует число d типа float из градусов в радианы
math.sin(x) Возвращает синус x в радианах
math.sinh(x) Возвращает гиперболический синус x в радианах
math.sqrt(x) Возвращает \( \sqrt{x} \)
math.tan(x) Возвращает тангенс x в радианах
math.tanh(x) Возвращает гиперболический тангенс x в радианах
math.trunc(x) Возвращает целую часть числа x в виде значения типа int; то же самое что и int(x)

Комплексные числа

Тип данных complex относится к категории неизменяемых и хранит пару значений типа float, одно из которых представляет действительную часть комплексного числа, а другое – мнимую. Литералы комплексных чисел записываются как действительная и мнимая части, объединенные знаком + или -, а за мнимой частью числа следует символ j. Вот примеры нескольких комплексных чисел: 3.5+2j, 0.5j, 4+0j, -1 - 3.7j. Обратите внимание, что если действительная часть числа равна 0, ее можно вообще опустить.

Отдельные части комплексного числа доступны в виде атрибутов real и imag. Например:

>>> z = -89.5+2.125j
>>> z.real, z.imag
(-89.5, 2.125)

За исключением //, %, divmod() и версии pow() с тремя аргументами все остальные арифметические операторы и функции, перечисленные в табл. 1 могут использоваться для работы с комплексными числами, так же как и соответствующие комбинированные операторы присваивания. Кроме того, значения типа complex имеют метод conjugate(), который изменяет знак мнимой части. Например:

>>> z.conjugate()
(-89.5-2.125j)

>>> 3-4j.conjugate()
(3+4j)

Тип данных complex может вызываться как функция – без аргументов она вернет значение 0j, с аргументом типа complex она вернет копию аргумента, а с аргументом любого другого типа она попытается преобразовать указанный объект в значение типа complex. При использовании для преобразования функция complex() принимает либо единственный строковый аргумент, либо одно или два значения типа float.

Если ей передается единственное значение типа float, возвращается комплексное число с мнимой частью, равной 0j.

Функции в модуле math не работают с комплексными числами. Это сделано преднамеренно, чтобы гарантировать, что пользователи модуля math будут получать исключения вместо получения комплексных чисел в некоторых случаях.

Если возникает необходимость использовать комплексные числа, можно воспользоваться модулем cmath, который содержит комплексные версии большинства тригонометрических и логарифмических функций, присутствующих в модуле math, плюс ряд функций, специально предназначенных для работы с комплексными числами, таких как cmath.phase(), cmath.polar() и cmath.rect(), а также константы cmath.pi и cmath.e, которые хранят те же самые значения типа float, что и родственные им константы в модуле math.

Числа типа Decimal

Во многих приложениях недостаток точности, свойственный числам типа float, не имеет существенного значения, и эта неточность окупается скоростью вычислений. Но в некоторых случаях предпочтение отдается точности, даже в обмен на снижение скорости работы. Модуль decimal реализует неизменяемый числовой тип Decimal, который представляет числа с задаваемой точностью. Вычисления с участием таких чисел производятся значительно медленнее, чем в случае использования значений типа float, но насколько это важно, будет зависеть от приложения.

Чтобы создать объект типа Decimal, необходимо импортировать модуль decimal. Например:

>>> import decimal
>>> a = decimal.Decimal(9876)
>>> b = decimal.Decimal("54321.012345678987654321")
>>> a + b
Decimal('64197.012345678987654321')