読者です 読者をやめる 読者になる 読者になる

倭算数理研究所

科学・数学・学習関連の記事を、「倭マン日記」とは別に書いていくのだ!

commons-math 解読 (11) : 複素数 Complex

commons-math

今回は複素数を表す Complex クラスを見ていきます。

クラス宣言

まずは Complex クラスのクラス宣言。 Complex は複素数体の元なので FieldElement インターフェースを実装しています。 ただし、複素数順序を持たないため、Real クラスのように Comparable は実装していません:

package org.apache.commons.math.complex;

public class Complex implements FieldElement<Complex>, Serializable{
    ...
}

インスタンス生成

Complex クラスのインスタンスを取得するには主に以下の方法があります:

  • 実部、虚部から (コンストラクタによる生成)
  • 極形式(絶対値と偏角)から (ComplexUtils#polar2Complex() メソッドによる生成)
  • 文字列(実部、虚部)から (ComplexFormat クラスによる生成)

実部、虚部から
まずは実部と虚部を指定して Complex オブジェクトを取得する方法。 これは Complex クラスのコンストラクタを用います:

public class Complex{

    public Complex(double real, double imaginary){ ... }

    ...
}

使い方は通常のコンストラクタの方法で:

def c = new Complex(1d, 2d)    // 1 + 2i

極形式から
次は極形式、つまり絶対値と偏角から Complex オブジェクトを取得する方法。 これには ComplexUtils クラスの static メソッド polar2Complex() を使います。 

public class ComplexUtils{
    public static Complex polar2Complex(double r, double theta){...}
}

第1引数は生成する複素数の絶対値なので非負でなければなりません(負の数を指定すると例外が投げられます)。 また、第2引数は偏角ですが、これは弧度法ラジアン)で指定する必要があります。 極形式についての簡単な説明はこの記事の最後で。 使い方はこんな感じ:

def c = new Complex(1d, Math.sqrt(3d))    // 1 + √3 i

// 実際には double の精度ではアサーションは通りませんが・・・
assert ComplexUtils.polar2Complex(2d, Math.PI/3d) == c  // 2exp(i π/3) = 2(cos(π/3) + i sin(π/3))

文字列(実部、虚部)から
インスタンス生成の最後は文字列からの変換。 これには ComplexFormat クラスを使います:

def format1 = new ComplexFormat()
// def format1 = ComplexFormat.getInstance() でも可。
assert format1.parse('1 + 2i') == new Complex(1d, 2d)

def format2 = new ComplexFormat('j')
assert format2.parse('1 + 2j') == new Complex(1d, 2d)

虚数単位の文字指定(デフォルトは "i")は ComplexFormat クラスのコンストラクタで指定できます。

メソッド

次はメソッドの挙動。 いくつかのグループに分類してますが、特に厳密な区分けがあるわけではありません。

  • 値評価
  • プロパティ取得など
  • 四則演算
  • 複素関数

値評価
まずは値の評価に関するメソッド。 概ね boolean 値を返すメソッドを集めてます:

public class Complex{
   ...

    boolean equals(Object other)
    int hashCode()

    boolean isInfinite()
    boolean isNaN()
}

equals() メソッドは通常の値オブジェクトとしての挙動に合うように、実部、虚部の値の等値評価を行います。 isInfinite(), isNaN() メソッドの挙動は以下のサンプルを参照:

// Infinity    どれも Complex.INF に等しい
assert new Complex(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY).isInfinite()
assert new Complex(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY).isInfinite()
assert new Complex(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY).isInfinite()
assert new Complex(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY).isInfinite()
assert new Complex(Double.POSITIVE_INFINITY, 1d).isInfinite()
assert new Complex(Double.NEGATIVE_INFINITY, 1d).isInfinite()
assert new Complex(1d, Double.POSITIVE_INFINITY).isInfinite()
assert new Complex(1d, Double.NEGATIVE_INFINITY).isInfinite()

// NaN    どれも Complex.NaN に等しい
assert new Complex(Double.NaN, Double.NaN).isNaN()
assert new Complex(Double.NaN, 1d).isNaN()
assert new Complex(1d, Double.NaN).isNaN()

プロパティ取得など
次は数値を返すメソッド

  • getReal(), getImaginary(), abs(), getArgument() ・・・ double 値を返す
  • negate(), conjugate() ・・・ Complex オブジェクトを返す
public class Complex{
   ...

    // 複素数体 ComplexField のインスタンスを取得
    ComplexField getField()

    // この Complex オブジェクトが表している複素数を z = x + yi とすると
    double getReal()    // 実部 x を返す
    double getImaginary()    // 虚部 y を返す
    double abs()    // 絶対値 |z| = √(x^2 + y^2) を返す
    double getArgument()    // 偏角 arg(z) = arctan(y / x) を返す

    Complex negate()    // -x - yi を返す
    Complex conjugate()    // 複素共役 x - yi を返す
}

メソッドの返り値の詳細(数学)はこの記事の最後参照。 また、getArgument() メソッドで返される偏角 { \theta } の値の範囲

  { \displaystyle
\begin{align*}
    -\pi < \theta \le \pi
\end{align*}
}

です。 詳しくは java.lang.Math#atan2(double, double) メソッドJavaDoc 参照。 各メソッドのサンプルはこんな感じ:

def c0 = new Complex(1d, 3d)    // = 1 + 3i

assert c0.getReal() == 1d    // = 1
assert c0.getImaginary() == 3d    // = 3
assert c0.abs() == Math.hypot(1d, 3d)    // = √(1^2 + 3^2)
assert c0.getArgument() == Math.atan(3d / 1d)    // = arctan(3)

assert c0.negate() == new Complex(-1d, -3d)    // = - 1 - 3i
assert c0.conjugate() == new Complex(1d, -3d)    // = 1 - 3i

四則演算
次は複素数の四則演算。 大半は FieldElement インターフェス()に定義されているメソッドのオーバーライドです。 multiply() メソッド(乗法)は double 値をとるものも定義されていますが。

public class Complex{
    ...

    Complex add(Complex rhs)
    Complex subtract(Complex rhs)
    Complex multiply(Complex rhs)
    Complex multiply(double rhs)
    Complex divide(Complex rhs)
}

使用方法は特に難しいことはないと思います:

def c0 = new Complex(1d, 3d)
def c1 = new Complex(3d, 4d)

assert c0.add(c1) == new Complex(4d, 7d)
assert c0.subtract(c1) == new Complex(-2d, -1d)
assert c0.multiply(c1) == new Complex(-9d, 13d)
assert c0.divide(c1) == new Complex(0.6d, 0.2d)

複素関数については次回以降に。

定義済み定数

Complex クラスに static フィールドとして定義されている複素数には以下のものがあります:

定数名
ZERO 0.0 + 0.0i (= 0 )
ONE 1.0 + 0.0i (= 1)
I 0.0 + 1.0i (= i )
INF +INF + INFi
NaN NaN + NaNi

ZERO と ONE は(Field インターフェースの実装クラスである) ComplexField クラスから getZero(), getOne() メソッドによって取得できます。

ちょっと数学を

複素数の演算等を数式で書いておきましょう。 以下、

  { \displaystyle
\begin{align*}
    z = x + yi \qquad(x,\,y \in \mathbf{R})
\end{align*}
}

とします。

極形式
複素数 { z } の絶対値 { r (= |z|) }偏角 { \theta (= \arg(z)) } は以下のように表されます:

  { \displaystyle
\begin{align*}
    \begin{cases}
        r = \left|z\right| = \sqrt{x^2 + y^2} \\
       \theta = \arg z = \tan^{-1}\left(\dfrac{y}{x}\right)
    \end{cases}
\end{align*}
}

これを逆に解くと

  { \displaystyle
\begin{align*}
    \begin{cases}
        x = r\cos\theta \\
        y = r\sin\theta
    \end{cases}
\end{align*}
}

となります。 ちなみに、極形式オイラーの公式を使うと簡単に書き下せます:

  { \displaystyle
\begin{align*}
     z &= r\cos\theta + i\sin\theta \\
       &= e^{i\theta}
\end{align*}
}

四則演算
2つの複素数の四則演算は以下のように計算できます({ a,\,b,\,c,\,d } は実数)

  { \displaystyle
\begin{align*}
    (a + bi) + (c + di) &= (a + c) + (b + d)i \\
    (a + bi) - (c + di) &= (a - c) + (b - d)i \\
    (a + bi) * (c + di) &= (ac - bd) + (ad + bc)i \\
    (a + bi) \;/\; (c + di) &= \frac{a + bi}{c + di} \\
        &= \frac{(a + bi)(c - di)}{c^2 + d^2} \\
        &= \frac{ac + bd}{c^2 + d^2} - \frac{ad - bc}{c^2 + d^2}i
\end{align*}
}

複素共役
複素数 z の複素共役 (complex conjugate) { \bar{z} } (もしくは { z^* })は

  { \displaystyle
\begin{align*}
    \bar{z} = x - yi
\end{align*}
}

で与えられます。 ある複素数とその共役な複素数との間の和と積は

  { \displaystyle
\begin{align*}
    z + \bar{z} &= 2x \\
    z \bar{z} &= x^2 + y^2 (= |z|^2)
\end{align*}
}

となり、どちらも実数になります。

数学ガール フェルマーの最終定理 (数学ガールシリーズ 2)

数学ガール フェルマーの最終定理 (数学ガールシリーズ 2)

Javaによるアルゴリズム事典

Javaによるアルゴリズム事典