コードを書けば複素数がわかる
TRANSCRIPT
コードを書けば複素数がわかる
@taketo1024
情報科学若手の会冬の陣 2015
i2 = �1
学校で習うとき1. 虚数単位 i = √-1 がいきなり出てくる
2. 複素数 z = a + bi の四則演算が定義される
3. 「数」と思っていた z がベクトルになってる
4. 「i をかけるのは90度回転です」などと教わる
5. 以後当たり前のように電流や波の方程式に出てくる
i =p�1
z = a+ bi
z
i
ちょっと待ってくれ… i は「想像上の数」なんだろ…?i
簡単なコードで i は 作れる!
i
方針
1. 複素数を最初から2次元ベクトルとして定義する。
2. i^2 = -1 となるように 掛け算を入れる。
3. これが実数を拡大した「数」になることを確認。
i2 = �1
1. 複素数を作ろう
まず2次元ベクトルから出発
struct Complex { let x: Double let y: Double init(_ x: Double, _ y: Double) { self.x = x self.y = y } }
言語:Swift
let z = Complex(2, 3)
x
y
2
z =
✓23
◆3
足し算・引き算・実数倍を定義func + (z: Complex, w: Complex) -> Complex { return Complex(z.x + w.x, z.y + w.y) }
prefix func -(z: Complex) -> Complex { return Complex(-z.x, -z.y); }
func - (z: Complex, z: Complex) -> Complex { return Complex(z.x - w.x, z.y - w.y) }
func * (a: Double, z: Complex) -> Complex { return Complex(a * z.x, a * z.y) }
演算子のオーバーライド
イコールを定義
struct Complex: Equatable { … }
func == (a: Complex, b: Complex) -> Bool { return (a.x == b.x) && (a.y == b.y) }
プロトコル(Java のインターフェース)
Complex(2, 3) + Complex(1, -1) == Complex(3, 2)
x
y
次に1 = (1, 0), i = (0, 1) として、
z = x + yi の形で書けるようにする。
1 =
✓10
◆i =
✓01
◆
z = a+ bi
Int, Double から Complex を生成
struct Complex: …, IntegerLiteralConvertible, FloatLiteralConvertible { … init(integerLiteral x: IntegerLiteralType) { self.x = Double(x) self.y = 0 } init(floatLiteral x: FloatLiteralType) { self.x = x self.y = 0 } }
リテラル限定の静的キャスト(Swift特有?)
これで実数を複素数に「埋め込んだ」ことになる。
x
y
1 =
✓10
◆
1 == Complex(1, 0) // true
あとは 定数 i を定義すれば…
let i = Complex(0, 1)
i
x
y
i =
✓01
◆
let z = 2 + 3 * i
x
y
求めていた表現を得る!
2 =
✓20
◆
3i =
✓03
◆z =
✓23
◆= 2 + 3i
さて、いよいよ掛け算の定義。
複素数の掛け算は、 実数と同じ計算規則を満たし、かつ、
となるように定義したい。
i2 = �1
z = a + bi, w = c + di として、積 zw は、z = a+ bi, w = c+ di
となる。 特に a = c = 0, b = d = 1 の場合が i^2 = -1 の式。
zw
zw = (a+ bi)(c+ di)
= a(c+ di) + bi(c+ di)分配法則
= ac+ adi+ bci+ bdi2
= ac+ adi+ bci� bd
分配法則
= ac� bd+ adi+ bci交換法則
= (ac� bd) + (ad+ bc)i分配法則
i2 = �1
i2 = �1
先の式で掛け算を定義してみると…
struct Complex { … }
func * (z: Complex, w: Complex) -> Complex { return Complex(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x) }
ちゃんと「掛け算の要件」を満たしている!
let α = 3 + 5 * i let β = -1 + 4 * i let γ = 4 - 7 * i
α * 1 == α // 1は単位元 α * β == β * α // 交換法則 (α * β) * γ == α * (β * γ) // 結合法則 α * (β + γ) == α * β + α * γ // 分配法則
当たり前に見えるが、テキトーに定義したのではこうならない。 これで安心して「数」として扱えるようになる。
i * i == -1 // true
できました!
割り算は?
zw = (ac� bd) + (ad+ bc)i = 1 + 0i
割り算は「逆数との積」なので、z の逆数 w は、
これを解けば、
w =1
a2 + b2(a� bi)
,(ac� bd = 1
ad+ bc = 0
z w
「逆数との積」で割り算を定義
struct Complex { … }
func / (z: Complex, w: Complex) -> Complex { let w_inv = (1 / (w.x * w.x + w.y * w.y) ) * Complex(w.x, -w.y) return z * w_inv }
複素数、できました!struct Complex: Equatable, IntegerLiteralConvertible, FloatLiteralConvertible { let x: Double let y: Double init(_ x: Double, _ y: Double) { self.x = x self.y = y } init(_ x: Double) { self.x = x self.y = 0 } init(integerLiteral x: IntegerLiteralType) { self.x = Double(x) self.y = 0 } init(floatLiteral x: FloatLiteralType) { self.x = x self.y = 0 } }
func == (z: Complex, w: Complex) -> Bool { return z.x == w.x && z.y == w.y }
func + (z: Complex, w: Complex) -> Complex { return Complex(z.x + w.x, z.y + w.y) }
func - (z: Complex, w: Complex) -> Complex { return Complex(z.x - w.x, z.y - w.y) }
prefix func -(z: Complex) -> Complex { return Complex(-z.x, -z.y); }
func * (a: Double, z: Complex) -> Complex { return Complex(a * z.x, a * z.y) }
func * (z: Complex, w: Complex) -> Complex { return Complex(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x) }
func / (z: Complex, w: Complex) -> Complex { let w_inv = Complex(w.x / (w.x * w.x + w.y * w.y), -w.y / (w.x * w.x + w.y * w.y)) return z * w_inv }
この掛け算こそが複素数の本質struct Complex: Equatable, IntegerLiteralConvertible, FloatLiteralConvertible { let x: Double let y: Double init(_ x: Double, _ y: Double) { self.x = x self.y = y } init(_ x: Double) { self.x = x self.y = 0 } init(integerLiteral x: IntegerLiteralType) { self.x = Double(x) self.y = 0 } init(floatLiteral x: FloatLiteralType) { self.x = x self.y = 0 } }
func == (z: Complex, w: Complex) -> Bool { return z.x == w.x && z.y == w.y }
func + (z: Complex, w: Complex) -> Complex { return Complex(z.x + w.x, z.y + w.y) }
func - (z: Complex, w: Complex) -> Complex { return Complex(z.x - w.x, z.y - w.y) }
prefix func -(z: Complex) -> Complex { return Complex(-z.x, -z.y); }
func * (a: Double, z: Complex) -> Complex { return Complex(a * z.x, a * z.y) }
func * (z: Complex, w: Complex) -> Complex { return Complex(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x) }
func / (z: Complex, w: Complex) -> Complex { let w_inv = Complex(w.x / (w.x * w.x + w.y * w.y), -w.y / (w.x * w.x + w.y * w.y)) return z * w_inv }
func * (z: Complex, w: Complex) -> Complex { return Complex(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x) }
2. 複素数を動かしてみよう
class ComplexPlane : UIView {
var unit: CGFloat = 50.0 var scale: CGFloat = 1.0 var points: [String: Complex] = [:] var colors: [String: UIColor] = [:] override func drawRect(rect: CGRect) { let ctx = UIGraphicsGetCurrentContext() let centerX = self.bounds.width / 2 let centerY = self.bounds.height / 2 // fill background CGContextSetFillColorWithColor(ctx, UIColor.whiteColor().CGColor) CGContextFillRect(ctx, self.bounds) // draw axises … } }
複素平面のViewクラス
let cplane = ComplexPlane(frame: …)
cplane["1"] = 1 cplane["i"] = i
let z = Complex(r: 2, θ: M_PI / 3) cplane["z"] = z
let w = z * z cplane["w"] = w
DEMO : 動かしてみよう!
z と w の関係は?z w
Re
Im
複素数 z は絶対値 r = |z|, 偏角 θ= arg(z) を用いて、 z = r(cosθ + i sin θ) と書ける(極表示)
r
✓
r = |z| ✓ = arg(z)z
z = r(cos✓ + isin✓)
z = r(cos✓ + isin✓)
rcos✓
irsin✓
複素数の掛け算を極表示で書き直してみると…
z = r(cos✓ + isin✓), w = s(cos�+ isin�)
zw = rs{(cos✓cos�� sin✓sin�) + i(sin✓cos�+ cos✓sin�)}
= rs(cos(✓ + �) + isin(✓ + �))
に対して、
つまり…、
加法定理
Re
Im
r
✓
sz
w
�
zw
rs
= rs(cos(✓ + �) + isin(✓ + �))zw
複素数の掛け算は「絶対値の積」×「偏角の和」だった!
Re
Im
特に i^2 = -1 は、 「90°回転を2回すれば180°回転」
i2 = �1
i90°
i2 = �1
3. なぜ複素数?
そもそもこれはどこから出て来た?
i2 = �1
(例) 方程式: x^2 + x + 1 = 0
-10 -7.5 -5 -2.5 0 2.5 5 7.5 10
-5
-2.5
2.5
5
x
2 + x+ 1 = 0
この方程式は実数の範囲では解を持たない。
y = x
2 + x+ 1
形式的に2方程式の解の公式を使うと、
ここで √-3 を √3 i と置き換えて:
は、x^2 + x + 1 = 0 の解になっている。
p�3
p3i
x
2 + x+ 1 = 0
x =�1±
p12 � 4 · 1 · 12
=�1±
p�3
2
↵ =�1 +
p3i
2,� =
�1�p3i
2
-10 -7.5 -5 -2.5 0 2.5 5 7.5 10
-5
-2.5
2.5
5
y = x
2 + x+ 1
y = x^2 + x + 1 は x = α, β で x 軸と交わっている…?y = x
2 + x+ 1 x = ↵,�
↵? �?
x, y を複素数と見てグラフを描くには、 残念ながら我々の世界では次元が1つ足りない。
Re
Im
Re
Im
zw
w = f(z) = z^2 + z + 1 を平面から平面への写像と見て、 z の動きにあわせて w がどう動くか見てみる。z w
w = f(z) = z2 + z + 1
代わりに、
f
DEMO
w = f(z) = z2 + z + 1
Re
Im
Re
Imz w
z を半径を大きくしながら円上で動かす。z
Re
Im
Re
Imz w
半径 1 のときに w = 0 となる点が2つある。w = 0
Re
Im
Re
Im
↵
�
z w
この2点が α, β で、f によって 0 に写されていた!↵,� f
↵ =�1 +
p3i
2,� =
�1�p3i
2
Re
Im
Re
Imz w
したがって…
一般の n 次式の場合も同様に、 z を 0 から大きくしていけば、w は必ず 0 を通る
↵
代数学の基本定理
複素数の範囲では必ず解を持つ!
anzn + ...+ a1z + a0 = 0n 次方程式 は、
コードを書いて自分で動かしてみれば、 数学はグッと身近になる!
「SwiftComplex」github で公開してます:
Thanks!@taketo1024