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

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


Создание комплексных чисел

Мы знаем, что любое комплексное число \(z\) задается его действительной частью \(a\), мнимой частью \(b\) и в нем всегда присутствует символ \(i\), обозначающий мнимую единицу. В Python все практически точно так же, только мнимую единицу обозначают символом j (реже J), а числа \(a\) и \(b\) могут быть любыми числами типа int или float. Например, комплексные числа в Python могут выглядеть следующим образом:

3 + 7j
100.0 + 0.001j
100. + .00j
2e-10 - 2e10j

Кстати, символ мнимой единицы j не может существовать сам по себе, к нему обязательно должно быть присоединено какое-то число, т.е. мнимая единица в Python выглядит как 1j, а любое число у которого действительная часть равна \(0\) можно указывать без нее, например, все вышеуказанные числа без действительной части, для интерпретатора Python считаются приемлемыми:

7j
0.001j
.00j
2e10j

Интуитивно понятно, что если в числе \(a+bi\) мнимая часть \(b=0\), то оно автоматически становится обычным вещественным числом, потому что \(z=a+0i=a\). Но в то же время все вещественные числа, являются подмножеством комплексных, значит число \(a\) это комплексное число у которого мнимая часть равна \(0\). В математике это так, но в Python нет, т.е. автоматического преобразования чисел типа complex в числа типа int или float не происходит, даже если их мнимая часть равна \(0\):

>>> 10 + 0j    #  это число могло бы стать числом типа int
(10+0j)
>>> 
>>> 3.14 - 0j    #  а это могло бы стать float
(3.14+0j)

Наверное, если бы такое преобразование имело место, то это привело бы к противоречиям некоторых математических операций. Здесь следует еще раз напомнить о преобразовании типов и о том что это преобразование работает только в одну сторону:

>>> -1/2    #  результат может быть только типа float
-0.5
>>> 
>>> (-0.5)**0.5    #  результат может быть только типа complex
(4.329780281177467e-17+0.7071067811865476j)

Именно поэтому, комплексное 3.14 - 0j не станет вещественным 3.14. Более того, даже если такие комплексные числа передать встроенным функциям int() или float(), то это приведет к ошибке:

>>> int(2 + 0j)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't convert complex to int

Для нас очевидно, что раз мнимая часть равна \(0\) то к типу int нужно приводить его действительную часть. Но то что очевидно для нас, не всегда очевидно для интерпретатора. Однако, мы можем работать с отдельными частями комплексного числа с помощью атрибутов real и imag. Так что наше преобразование можно записать так:

>>> int((2 + 0j).real)
2

Особо бдительные, могут заметить, что можно вообще обойтись без функции int() потому что действительная часть числа и так типа int, однако, атрибуты real и imag всегда возвращают вещественный результат (тип float).


Встроенная функция complex()

Встроенная функция complex(real[, imag]) позволяет создать комплексное число на основе значений его действительной и мнимой частей:

>>> complex(1)    #  аргумент imag не обязателен
(1+0j)
>>> 
>>> complex(1, 2e-2)
(1+0.02j)

Приятным сюрпризом данной функции, является то что она может создавать комплексное число из строки. Но с небольшой оговоркой, эта строка должна быть допустимым литералом комплексного числа:

>>> complex('1+2j')
(1+2j)
>>> complex('0.1+2.0j')
(0.1+2j)
>>> complex('.1+2.j')
(0.1+2j)
>>> complex('1e3+2e-3j')
(1000+0.002j)

Учитывая, что на месте действительной и мнимой части могут находиться только целые и вещественные числа, то как видите, способов ввода строк комплексных чисел становится довольно много. Однако, следует помнить, что пробельные символы являются недопустимыми:

>>> complex('1e3 + 2e-3j')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: complex() arg is a malformed string

Представление на комплексной плоскости

Целые и вещественные числа в геометрическом представлении являются точками на числовом луче:

Python числа int и float

Геометрическим представлением комплексных чисел являются точки на плоскости. Данная плоскость называется комплексной и абсолютно аналогична прямоугольной системе координат, только по оси абцис (x) откладывается величина действительной части (real), а по оси ординат (y) ооткладывается величина мнимой части (imag). Например, точка \(A(3, 4)\) и комплексное число \(z = 3 + 4i\) будут выглядеть вот так:

Комплексные числа в Python тип complex

Как видите, изображение комплексных чисел на плоскости довольно простой процесс, по сути это те же самые декартовы координаты, которые получаются по правилу: \(x=\mathrm {Re} \,z; \quad y=\mathrm {Im} \,z\). А в Python получение тех же координат будет выглядеть аналогично:

>>> z = 3 + 4j
>>> 
>>> z.real, z.imag
(3.0, 4.0)

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


Арифметические операции

Сложение двух комплексных чисел \(A=a+bi\) и \(B=c+di\) выполняется по простой формуле:

$$A+B = \left(a+bi\right)+\left(c+di\right)=\left(a+c\right)+\left(b+d\right)i$$

Python выполняет все промежуточные действия за нас, сразу выдавая результат:

>>> a = -5 + 3j
>>> b = 4 + 2j
>>> 
>>> a + b
(-1+5j)

На предыдущем рисунке мы видели, что комплексное число это точка на комплексной плоскости. Но если заметить что значения действительной и мнимой части отсчитываются от начала координат, то уместно задать вопрос: "А нельзя ли изображать эти числа в виде векторов?" Ответ: "Можно." Данные числа действительно можно рассматривать как радиус-векторы:

Комплексные числа в Python тип complex

Разность комплексных чисел задается похожим образом:

$$A-B = \left(a+bi\right)-\left(c+di\right)=\left(a-c\right)+\left(b-d\right)i$$

Умножение комплексного числа на вещественное число выполняется очень просто: \(kA = k\left(a+bi\right)=ka + kbi\) и по сути просто меняет лишь длину радиус вектора комплексного числа, вдоль его направления:

>>> a = 2 + 2j
>>> 
>>> a*2
(4+4j)
>>> 
>>> a*(-1.5)
(-3-3j)

Комплексные числа в Python тип complex

А вот умножение комплексных чисел друг на друга немного сложнее:

$$(a+bi)\cdot (c+di)=(ac-bd)+(bc+ad)i$$

Как всегда в Python мы сразу видим результат:

>>> a = 1 + 1j
>>> b = 1 + 4j
>>> 
>>> a*b
(-3+5j)

Который на комплексной плоскости выглядит вот так:

Комплексные числа в Python тип complex

Можно было бы предположить, что это что-то вроде векторного или скалярного умножения векторов, но нет, умножение комплексных чисел, отличается от этих двух операций.

Деление комплексного числа на вещественное число, так же как и умножение на вещественное число кроме длины радиус-вектора вдоль его направления ничего не меняет:

>>> a = 3 + 8j
>>> 
>>> a/2
(1.5+4j)

Деление комплексных чисел друг на друга вычисляется по формуле:

$${\frac {a+bi}{c+di}}={\frac {ac+bd}{c^{2}+d^{2}}}+\left({\frac {bc-ad}{c^{2}+d^{2}}}\right)i$$

Давайте посмотрим как это выглядит в Python и на комплексной плоскости:

>>> a = -5 - 1j
>>> b = -1 + 1j
>>> 
>>> a/b
(2+3j)

Комплексные числа в Python тип complex


Математические операции

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

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

Операция Результат Замечание
1 x ** y возводит x в степень y (I)
2 pow(x, y[, z]) возводит x в степень y по модулю z, где z – необязательный аргумент. Если указан параметр z, то это приведет к ошибке ValueError (I)
3 divmod(x, y) возвращает кортеж с парой чисел (x // y, x % y) (II)
4 x.conjugate() возвращает \(\bar{x}\) - число, которое комплексно сопряжено с \(x\)
5 complex(re, im) преобразует re в комплексное число (по умолчанию im = 0) (V)(VI)
6 float(x) преобразует x в вещественное число (число с плавающей точкой). Если x комплексное, то будет вызвано исключение TypeError (VI)
7 int(x) переобразует x в целое число, представленное в десятичной системе счисления. Если x комплексное, то будет вызвано исключение TypeError (VI)
8 abs(x) абсолютное значение (модуль) числа x (III)
9 +x делает число x положительным
10 -x делает число x отрицательным
11 x % y остаток от деления x на y (II)
12 x // y результат целочисленного деления x на y (II)
13 x / y результат "истинного" деления x на y (IV)
14 x * y произведение x и y
15 x - y разность x и y
16 x + y сумма x и y

Важно: приоритет математических операций выше операций сравнения.

Замечания:

I. возведение \(0+0i\) в степень \(0+0i\) возвращает \(1+0i\):

>>> c = 0 + 0j
>>> c
0j
>>> 
>>> c**c
(1+0j)

II. функция divmod() и операция %, // не работают для комплексных чисел. Для вас это может быть и очевидно, но не пользователя для которого вы пишите программу.

III. Функция abs() всегда возвращает результат типа float.

IV. деление на \(0+0i\) приведет к ошибке и вызовет исключение ZeroDivisionError.

V. встроенная функция complex() пропускает числа (объекты) типа complex "как есть", не выполняя над ними, абсолютно никаких действий.

VI. строго говоря эти функции не являются математическими, но они могут учавствовать в математических выражениях Python и поэтому должны обладать приоритетом.


Операции сравнения

Для сравнения чисел имеется \(8\) операций, но для комплексных чисел доступно только \(4\) причем все они имеют одинаковый приоритет:

Операция Результат Замечание
1 x < y True если x меньше y, иначе False (I)
2 x <= y True если x меньше или равно y, иначе False (I)
3 x > n True если x больше y, иначе False (I)
4 x >= n True если x больше или равно y, иначе False (I)
5 x == y True если x равно y, иначе False
6 x != y True если x не равно y, иначе False
7 x is y True если x и y это один и тот же объект, иначе False
8 x is not y True если x и y это не один и тот же объект, иначе False

Важно: приоритет операций сравнения ниже математических.

I. эти операции могут присутствовать в математических выражениях, но если операндами являются комплексные числа или одним из них является комплексным числом, то это приведет к ошибке и вызовет исключение TypeError:

>>> a = 3+4j
>>> 
>>> abs(a) < 6
True
>>> 
>>> a < 6
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: complex() < int()