Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
Ruby TIPS

Ruby TIPS

複素数(Complexクラス)を活用するには?

2016年3月29日

組み込みライブラリに含まれるComplexクラスによる基本的な複素数の取り扱い方、複素数の四則演算、平面上の点(ベクトル)の操作方法を説明する。

ローグ・インターナショナル 羽山 博
  • このエントリーをはてなブックマークに追加

 Rubyには他のプログラミング言語にはない便利な機能が数多く備わっている。その一つが組み込みライブラリに含まれる複素数(=Complex)クラス。複素数にはなじみのない人も多いかもしれないが、平面上の点やベクトルの操作がとても簡単になる。今回は複素数の基本から活用までを見てみよう。

Complexクラスを利用して複素数を扱う

 複素数とは、実数部虚数部を持つ値である。「虚数」という言葉にはつかみどころのないイメージが伴うので、この時点でアレルギーを起こしてしまう(しまった)人もいるかもしれない。が、単に、2つの値を組み合わせたものでしかない。つまり、数の要を持つだから「複素数」というわけだ。数式では、4 + 3iのように表されるが、2つの値を「+」でつないだだけのことで、足し算と区別するために2つめの値にiを付けて表しているのだ、と気楽に考えよう。前の方には実数部、後ろの方には虚数部といういかめしい名前が付いているが、ちょっとカッコよく言いたかっただけ、と思っていればよい。

 そんなヘンテコな値が受験生を苦しめる以外に何の役に立つのか、と思われる向きも多いだろうが、実は、平面の座標を扱う場合など、日常業務の中で役に立つ場合が結構ある。それについての説明は本稿の後半に譲ることとして、まずは複素数の基本的な扱い方から見てみよう。複素数なんて自分には関係ないや、とそっぽを向かずに読み進めるときっといいことがある(はずだ)。

 では、始めよう。Rubyでは組み込みライブラリのComplexクラスを利用すれば、複素数をそのまま取り扱える。ただし基本的なComplexクラスのオブジェクト(以下、Complexオブジェクトと略記)の生成では、数学的な書き方ではなく、以下のようにComplex(実数部, 虚数部)という形式で表す。

図1.1 Complexオブジェクトを作成する
図1.1 Complexオブジェクトを作成する

Complex(=Kernelモジュール関数)の引数に実数部と虚数部を指定するだけでComplexオブジェクトが作成できる。ここでは2.0+1.0iという値を持つ複素数を作成した。

 これでComplexオブジェクトが作成される。また、複素数を文字列として記述し、to_cメソッドを利用してもComplexオブジェクトを作成できる。簡単な例で基本的な取り扱い方を見ておこう。

sample001.rb
# 複素数の利用例
x = Complex(2.0, 1.0) 
p x

# 文字列を複素数にする
p "1.2-3.4i".to_c

# 実数部を取り出す
p x.real

# 虚数部を取り出す
p x.imaginary

# 実数部と虚数部の配列を作る
p x.rect

# iの2乗
p Complex(0, 1) ** 2
リスト1.1 複素数を作成し、実数部と虚数部を取り出す

複素数を作り、realメソッドで実数部を取り出したり、imaginaryメソッドで虚数部を取り出したりしている。また、rectメソッドを利用すれば、実数部と虚数部を配列として返すこともできる。

 実行例は以下の通り。複素数は、単に2つの値を組み合わせただけのもの、という気持ちで見れば簡単。もちろん、それぞれの値を個別に扱うこともできる。

コンソール
$ ruby sample001.rb 
(2.0+1.0i)
(1.2-3.4i)
2.0
1.0
[2.0, 1.0]
(-1+0i)
$ 
実行例1.1 複素数の作成と利用の例

リスト1.1のコードと対比しながら、どの値が実数部でどの値が虚数部であるかを確認しよう。最後に複素数を2乗した例も示したが、これについては次のコラムを参照してほしい。

【コラム】虚数って何

 複素数と聞いただけでアレルギーを起こす人でも、高校時代に「2乗すると-1になる値をiと表し、それを虚数単位と呼ぶ」と習ったことぐらいは覚えているだろう。リスト1.1と実行例1.1の最後でそれが確認できる。しかし「2乗すると-1になる」というのはどうもイメージが湧きにくい。ところが、「同じことを2回やると反対の向きになる」と考えれば、誰でもそういう事柄を体験していることが分かる。例えば、小学校の運動会の練習でさんざんやらされた「右向け右」がその一つだ。前を向いている人が1回「右向け右」をすると、右方向を向くことになる。さらにもう1回「右向け右」をすると後ろ向きになる。もう少し数学的に表して、実数部をx軸とし、虚数部をy軸とすればそれと同じことができる(ただし、回転の向きは「右向け右」とは逆)。

虚数のイメージ

 1を複素数の形式に沿って表せば1+0iとなり、i0+1iとなる。また、-1-1+0iとなる。Rubyの書き方だと1Complex(1,0)iComplex(0,1)-1Complex(-1,0)となる。()の中の値と上の図の座標とを見比べると、ぴったり一致していることが分かる。

複素数の四則演算

 複素数の場合でも、四則演算のための演算子は「+」「-」「*」「/」を使う。一般的な数値の計算と同じなので、書き方は簡単。サクッと例で確認しておこう。

sample002.rb
x = Complex(2.0, 1.0)
y = Complex(3.0, 4.0)

# 四則演算
p x+y   # 加算
p x-y   # 減算
p x*y   # 乗算
p x/y   # 除算
p x**2  # べき乗
リスト1.2 複素数の四則演算

複素数の四則演算を試してみる。除算の場合、実数部と虚数部の両方が0になっている値で割ることはできないことに注意(ゼロ除算エラーとなる)。

 実行例は以下の通り。

コンソール
$ ruby sample002.rb 
(5.0+5.0i)
(-1.0-3.0i)
(2.0+11.0i)
(0.4-0.2i)
(3.0+4.0i)
$
実行例1.2 複素数の四則演算の実行例

加算と減算は実数部同士、虚数部同士で計算すればいいので、簡単に理解できる。乗算と除算は少し複雑なルールになっているので後のコラムを参照。

【コラム】複素数の演算

 複素数の四則演算の方法を簡単に紹介しておこう。2つの複素数をa+bic+diとすると以下のようになる

  • 加算: (a+bi) + (c+di) = (a+c) + (b+d)i
  • 減算: (a+bi) - (c+di) = (a-c) + (b-d)i
  • 乗算: (a+bi) * (c+di) = (ac-bd) + (bc+ad)i
  • 除算: (a+bi) / (c+di) = ((ac+bd) + (bc-ad)i)/(c2+d2)

 リスト1.2の乗算を手計算でやってみると、aが2.0、bが1.0、cが3.0、dが4.0なので、

  • 実数部は (2.0*3.0-1.0*4.0) から、2.0
  • 虚数部は (1.0*3.0+2.0*4.0) から、11.0

が求められる。従って答えは、

  • 2.0+11.0i

となる。この結果は実行例1.2と一致している。

 除算はさらに複雑だが、まず分子を求めると、

  • 実数部は (2.0*3.0+1.0*4.0) から、10.0
  • 虚数部は (1.0*3.0-2.0*4.0) から、-5.0

となる。

 分母は、

  • (3.02+4.02) から、25.0

となるので、答えは、

  • 10.0/25.0-5.0/25.0i、つまり0.4-0.2i

となる。この結果も実行例1.2と一致している。

平面上の点(ベクトル)を伸長させるには

 では、ここから実用的な例を見ることにしよう。複素数を使えば、四則演算だけで平面上の点(=原点からその点へのベクトル)を簡単に伸長させたり回転させたりできる。そのためには、複素数の実数部をx座標、虚数部をy座標と考えるとよい。このような平面を複素平面と呼ぶ。例えば、2.0+1.0iならば、x座標が2.0、y座標が1.0の点と考えられる。まずは、ベクトルの伸長から見てみよう。

図1.3 複素数は複素平面上の点として表される
図1.3 複素数は複素平面上の点として表される

複素数は2つの値の組み合わせなので、1つ(実数部)をx軸の値、もう一つ(虚数部)をy軸の値と考えれば、2.0+1.0i(2.0, 1.0)という座標で表される。

 複素数で表される点は原点からのベクトルと考えられるので、以下、単にベクトルと呼ぶことにする。さて、そのベクトルを伸長させるには、数値(実数)を掛けてやるだけでよい。例えば、長さを2倍にするなら、2を掛けるとよい。

sample003.rb
# 複素平面上の座標(ベクトル)
x = Complex(2.0, 1.0)

# ベクトルを2倍に伸長する
p x*2
リスト1.3 ベクトルを伸長する

複素数を何倍かすればベクトルの長さを何倍かにできる。当然のことながら、1より小さい値を掛ければ縮小できる。

 実行例は以下の通り。(2.0, 1.0)という点が(4.0, 2.0)になったことが分かる。

コンソール
$ ruby sample003.rb 
(4.0+2.0i)
$
実行例1.3 ベクトルを伸長させた例

複素数に実数を掛けるときには、実数部と虚数部にそれぞれの値を掛けるだけでよい。これだけでベクトルを伸長できる。

平面上の点(ベクトル)を回転させるには

 複素平面上での回転も簡単。角度θだけ反時計回りに回転させるときには、cosθ + isinθを掛ければよい。角度の単位はラジアンなので、例えば90度回転させたいときにはcos(π/2)+isin(π/2)を掛ける。例を見てみよう。

sample004.rb
include(Math)  # 定数PIを使うためにインクルード

# 複素平面上の座標(ベクトル)
x = Complex(2.0, 1.0)

# 回転させる
theta = PI/2
p x * Complex(cos(theta), sin(theta))

p x * Complex.polar(1.0, theta)   # この方法も使える
リスト1.4 ベクトルを回転させる

cosθ + isinθを掛けてベクトルを回転させる。ここでは90度つまりπ/2だけ回転させる。なお、polarメソッドを使うと、長さと偏角を指定して複素数を作成できる。長さ1、偏角θの複素数を掛けても同様に回転できる。

 実行結果は以下の通り。(2.0, 1.0)(-1.0, 2.0)に変換された(=回転した)。

コンソール
$ ruby sample004.rb 
(-0.9999999999999999+2.0i)
(-0.9999999999999999+2.0i)
$
実行例1.4 ベクトルを回転させた例

計算にπの近似値を使っているので誤差が出るが、結果は-1.0+2.0iになっていることが分かる。誤差が気になるなら、必要な有効桁数まで求められるように四捨五入すればよい。

 元のベクトルと回転後のベクトルを図で表してみると以下のようになる。視覚的にも90度回転していることが確認できる。

ベクトルを回転させる
図1.4 元の座標と回転後の座標をプロットしてみる

元の点の座標は(2.0, 1.0)。青い矢印で示したベクトルがそれである。回転後の点の座標は(-1.0, 2.0)。こちらは赤い矢印で示した。確かに90度回転している。

【コラム】偏角を使って複素数を表す

 複素数を複素平面上の点と考えると、xy座標ではなく、原点からの距離(長さ)とx軸からの偏角(反時計回りの角度)で表すこともできる。例えば(1,1)という点は、長さがルート2で偏角が45度(=π/4)の点とも言える。

長さと偏角で複素数を表す
長さと偏角で複素数を表す

 リスト1.4で示したように、polarメソッドを使うと、長さと偏角を指定して複素数を作成できる。なお、複素数の長さを得るにはabsメソッドまたはmagnitudeメソッドを使い、偏角を得るにはargメソッド、angleメソッドまたはphaseメソッドを使う(いずれもComplexクラスのインスタンスメソッドなので、x.absのようにして使えばよい)。

 上で見た方法を知っていれば、点(ベクトル)をどんな角度にも回転させられる。逆に、時計回りに回転させるには、乗算ではなく除算を使えばよい。さらに前項で見たように実数を掛ければ、回転と伸長を同時に実行できる。

 ただし、原点に対して対称移動するには-1を掛けるだけでよい。また、x軸に対して対称移動するだけなら共役複素数を求めるconjメソッドを使うとよい。共役複素数とは、実数部は同じで虚数部の符号が異なる複素数のことである。これらについても例を示しておこう。

sample005.rb
# 複素平面上の座標(ベクトル)
x = Complex(2.0, 1.0)

# 原点に対して対称なベクトルを求める
p -x

# X軸に対して対称なベクトルを求める(共役複素数を求める)
p x.conj
リスト1.5 ベクトルを対称移動させる

原点に対して対称移動するには実数部と虚数部の符号を両方とも変えてやればよい。また、x軸に対して対称移動するには、conjメソッドを使って虚数部の符号が異なる共役複素数を求めればよい。

 実行例は以下の通り。元の(2.0, 1.0)という点が原点に対して対称な(-2.0, -1.0)に移動できる。また、x軸に対して対称な(2.0, -1.0)にも簡単に移動できる。

コンソール
$ ruby sample005.rb
(-2.0-1.0i)
(2.0-1.0i)
$
実行例1.5 ベクトルを対称移動させた例

対称移動の場合は回転させなくても簡単にできる。なお、y軸に対して対称移動するにはx軸に対して対称移動させた後、原点に対して対称移動させればよい。

まとめ

 Rubyでは、複素数を取り扱うためのComplexクラスが組み込みライブラリとして提供されている。今回は、一般にはあまりなじみのない複素数も、平面の座標という比較的身近な問題に使えることを見た。実数部と虚数部を指定してComplexクラスのオブジェクトを作成すれば、四則演算だけでベクトルの伸長や回転ができることが体感していただけたかと思う。

処理対象:複素数 カテゴリ:数学
API:Mathモジュール|Kernelモジュール カテゴリ:組み込みライブラリ
API:Complex クラス カテゴリ:Kernelモジュール

※以下では、本稿の前後を合わせて5回分(第5回~第9回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

Ruby TIPS
5. 代入により決まる変数のデータ型/丸め誤差が発生する浮動小数点数の比較― コーディングミスを防ぐには?(3)

初心者向けにRubyプログラミングの落とし穴を紹介。代入する値により変数のデータ型が決まることに関する注意点と、浮動小数点数の比較における丸め誤差の問題と回避方法について説明する。

Ruby TIPS
6. 桁区切り数値の記述と出力/文字列を逆順に/文字列の分割/文字列の各文字の利用 ― コーディングミスを防ぐには?(4)

初心者向けにRubyプログラミングの落とし穴を紹介。桁区切り指定で数値リテラルを記述する方法や、桁区切りの数値文字列を出力する方法、文字列を逆順に並べ替える方法、文字列を文字列で分割する方法、文字列を1文字ずつ扱う方法を説明する。

Ruby TIPS
7. 【現在、表示中】≫ 複素数(Complexクラス)を活用するには?

組み込みライブラリに含まれるComplexクラスによる基本的な複素数の取り扱い方、複素数の四則演算、平面上の点(ベクトル)の操作方法を説明する。

Ruby TIPS
8. if修飾子/unless文/case文 ― ちょっと便利な条件分岐の構文とは?

Rubyには、if文のような一般的なもの以外にも、if修飾子/unless文/unless修飾子/case文といった便利な条件分岐の構文が用意されている。その基本的な使い方を解説。

Ruby TIPS
9. while修飾子/until修飾子/while文/until文 ― ちょっと便利な繰り返し処理の構文とは?(1)

Rubyに用意されている繰り返し処理構文のうち、while文/until文の基本的な使い方と落とし穴を解説。また、while/until「修飾子」を使った簡潔な記述方法にも言及する。

サイトからのお知らせ

Twitterでつぶやこう!