Complex numbersは複雑ではありません。
ただ、もっと穏やかな名前が必要なだけです。
これらはとても便利なので(特に工学や科学では)、Pythonでは整数(int)や浮動小数点数(float)と並んで、complexが標準の数値型として用意されています。
Pythonのcomplex値は、本質的には2つの浮動小数点数のペアです。
これらは、残念な歴史的経緯から「実部」と「虚部」と呼ばれています。
繰り返しになりますが、奇妙な名前にとらわれず、その根底にあるシンプルさに目を向けるのがよいでしょう。
複素数を作るには、よく使われる方法が2つあります。
complex(real, imag)コンストラクターは、2つのfloat仮引数を取ります:>>> z1 = complex(1.5, 2.0)
>>> z1
(1.5+2j)
このコンストラクターは、文字列の入力を解析することもできます。 ただし、文字列にスペースが含まれていると失敗するという、奇妙な制限があります。
>>> complex('4+2j')
(4+2j)
>>> complex('4 + 2j')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: complex() arg is a malformed string
<real part> + <complex part>jというリテラルとして指定できます。実部が0の場合は、<complex part>jだけでもかまいません:>>> z2 = 2.0 + 1.5j
>>> z2
(2+1.5j)
最終的な結果は、complex()コンストラクターを使った場合とまったく同じです。
複素数の虚部には、2つのルールがあります:
jで表します(数学の教科書で見かけるiではありません)。
jは必ず数値の直後に付けます。そうしないと、Pythonが変数名として解釈してしまいます。必要なら1jを使います。
>>> j
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'j' is not defined
>>> 1j
1j
>>> type(1j)
<class 'complex'>
ほとんどのエンジニアはjで満足しています。
科学者や数学者の多くは、_虚数_を意味する数学的な記法iを好みます。しかし、その記法は電気工学で_電流_を意味するiと衝突します。
そのため、Pythonの設計では電気エンジニアの意見が通りました。
複素数の各部に個別にアクセスするには:
>>> z2.real
2.0
>>> z2.imag
1.5
どちらの部分も0になり得るので、数学者はその数を「純実数」や「純虚数」と呼ぶことがあります。 しかし、Pythonではそれでも複素数です:
>>> complex(0, 1)
1j
>>> type(complex(0, 1))
<class 'complex'>
>>> complex(1, 0)
(1+0j)
「i(またはj)は-1の平方根です」と聞いたことがあるかもしれません。
今のところ、これが意味するのは、虚部が_定義により_次の等式を満たすということだけです。
1j * 1j == -1 # => True
これはシンプルな考え方ですが、興味深い結果を生みます。
浮動小数点数や整数で使われるoperatorsのほとんどは、複素数でも機能します:
>>> z1 = (1.5+2j)
>>> z2 = (2+1.5j)
>>> z1 + z2 # addition
(3.5+3.5j)
>>> z1 - z2 # subtraction
(-0.5+0.5j)
>>> z1 * z2 # multiplication
6.25j
>>> z1 / z2 # division
(0.96+0.28j)
>>> z1 ** 2 # exponentiation
(-1.75+6j)
>>> 2 ** z1 # another exponentiation
(0.5188946835878313+2.7804223253571183j)
>>> 1j ** 2 # j * j == -1
(-1+0j)
複素数の掛け算と割り算のルールを説明することは、この概念の範囲外です(そして、そうした演算を「手作業で」行うことは、それほど頻繁にはないでしょう)。
複素数についてもっと深く知りたければ、数学や電気工学の入門書がこの内容を扱っています。
また、ExercismにはComplex Numbersという演習があり、こうした演算を行う複素数のクラスを基本原理から実装できます。
複素数では整数除算は___できません___。そのため、//演算子と%演算子、そしてdivmod()関数は、複素数の型ではエラーになります。
数値型に実装されている関数の中で、複素数を扱うときに非常に便利なものが2つあります:
<complex number>.conjugate()は、複素数の虚部の符号を反転するだけです(+から-へ、またはその逆へ)。
abs(<complex number>)は、虚部のない実数を返すことが保証されています。>>> z1
(1.5+2j)
>>> z1.conjugate() # flip the z1.imag sign
(1.5-2j)
>>> abs(z1) # sqrt(z1.real ** 2 + z1.imag ** 2)
2.5
cmathモジュールPythonの標準ライブラリには、実数を扱うための便利な機能が満載のmathモジュールがあります。
複素数を扱うための、同等のcmathモジュールも用意されています。
このモジュールをひととおり読んで、いろいろ試してみることをおすすめします。主な分類は次のとおりです:
いくつかの定数を使った例を紹介します:
>>> import cmath
>>> euler = cmath.exp(1j * cmath.pi) # Euler's equation
>>> euler.real
-1.0
>>> round(euler.imag, 15) # round to 15 decimal places
0.0
このように、自然界で最も重要な定数のうち3つ、e、i(またはj)、piを使ったシンプルな式が-1という結果を生みます。
これは数学全体の中で最も美しい結果だと信じている人もいます。
その起源は1740年ごろにさかのぼります。
興味がなければ、この部分は飛ばしてかまいません。
16世紀には、確かに奇妙で新しいものでした。
500年後の今では、工学や自然科学のほとんどで中心的な役割を担っています。
実は、複素数は、回転するものや波のような性質を持つものを記述するための最もシンプルな方法です。 そのため、電気工学、音声処理、物理学、コンピューターゲーム、ナビゲーションなど、ほんの一部を挙げただけでも幅広く使われています。
ものごとが回転するのは目で見てわかります。 複素数は世界を回しているわけではありませんが、世界が回る結果として_何が起こるか_を説明するのにはうってつけです。大きな嵐の衛星画像を見てみてください。
もっと気づきにくいことですが、音は波のようであり、光も波のようであり、電波も波のようです。そして、母国の経済でさえ、少なくとも部分的には波のようなものです。
こうした波の処理の多くは三角関数(sin()とcos())で行えますが、すぐに煩雑になります。
複素指数関数を使うほうが、___はるかに___扱いやすくなります。
それが当てはまるのは、洞窟に住んで食料を採集して暮らしている場合だけです。
何らかの画面でこれを読んでいるなら、複素数を使うことで成し遂げられた20世紀の有用な進歩に、完全に依存していることになります。
半導体チップ。
高速フーリエ変換(FFT)アルゴリズム。
FFTは複素数の応用であり、音声伝送、音声処理、写真、動画に関わる_あらゆるもの_に使われています。
MP3などの音声フォーマットは圧縮にFFTを使い、より小さなストレージ容量により多くの音声を収められるようにしています。
JPEG圧縮やMP4動画をはじめ、他にも多くの画像・動画フォーマットが圧縮にFFTを使っています。
FFTは、携帯電話の基地局が自分の携帯電話の信号を他のすべての人の信号から分離するためのデジタルフィルターにも使われています。
つまり、毎秒何千回もの複素数計算に依存する技術を、おそらく使っているのです。