虚数は作れる!swift で学ぶ複素数
DESCRIPTION
TRANSCRIPT
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
そんな長年のモヤモヤを今日は全て解消します!
方針
1. 複素数を最初から2次元ベクトルとして定義する。
2. i^2 = -1 となるように 掛け算を入れる。
3. これが実数を拡大した「数」になることを確認。
→ 虚数単位 i の「実在を信じる」ことを一切求めない!
i2 = �1
i
で。
1. 複素数を作る
まず2次元ベクトルから出発
struct Complex { let x: Double let y: Double init(_ x: Double, _ y: Double) { self.x = x self.y = y } }
let z = Complex(2, 3)
x
y
2
z =
✓23
◆3
イコールを定義
struct Complex: Equatable { … } !func == (a: Complex, b: Complex) -> Bool { return (a.x == b.x) && (a.y == b.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 - (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) }
次に1 = (1, 0), i = (0, 1) として、
z = x + yi の形で書けるようにする。
1 =
✓10
◆i =
✓01
◆
z = a+ bi
まず Double から生成できるようにする
struct Complex: …, FloatLiteralConvertible { … static func convertFromFloatLiteral(value: FloatLiteralType) -> Complex { ! return Complex(value, 0) } }
let z: Complex = 2.0 // Complex(2.0, 0)
struct Complex: …, IntegerLiteralConvertible { … static func convertFromIntegerLiteral(value: IntegerLiteralType) -> Complex { ! return Complex(Double(value), 0) } }
let z: Complex = -1 // Complex(-1, 0)
Int についてもやっておく
これで実数を複素数に「埋め込んだ」ことになる。
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
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分配法則
とするしかない。
zw
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
YES!
割り算も逆数との積で定義
struct Complex { … } !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 } static func convertFromIntegerLiteral(value: IntegerLiteralType) -> Complex { return Complex(Double(value), 0) } static func convertFromFloatLiteral(value: FloatLiteralType) -> Complex { return Complex(value, 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) } !prefix func -(z: Complex) -> Complex { return Complex(-z.x, -z.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, 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 } !let i = Complex(0, -1)
「実在しない数」 など出てきませんね?
2. さわれる複素数
Re
Im
複素数をおく xy 平面を「複素平面」と呼び、 x軸を実軸、y軸を虚軸と呼ぶ
z = 2 + 3i
2
3i
Re
Im
複素数 z に対して、 絶対値 r = |z|, 偏角 θ= arg(z) が定義できる
r
✓
r = |z| ✓ = arg(z)
z
z
実装しときましょう
func abs(z: Complex) -> Double { return sqrt(z.x * z.x + z.y * z.y) } !func arg(z: Complex) -> Double { let r = abs(z) if(r == 0) { return 0 } let t = acos(z.x / r) return (z.y >= 0) ? t : 2 * M_PI - t }
複素数は絶対値と偏角で書ける(極表示)
Re
Im
r
✓
z = r(cos✓ + isin✓)
rcos✓
irsin✓
実装しときましょう
struct Complex: … { let x: Double let y: Double init(_ x: Double, _ y: Double) { self.x = x self.y = y } init(r: Double, θ: Double) { self.x = r * cos(θ) self.y = r * sin(θ) } ! … }
さて、極表示で掛け算を書き直してみると…
z = r(cos✓ + isin✓), w = s(cos�+ isin�)
zw = rs{(cos✓cos�� sin✓sin�) + i(sin✓cos�+ cos✓sin�)}= cos(✓ + �) = sin(✓ + �)
= rs(cos(✓ + �) + isin(✓ + �))
に対して、
つまり…、
Re
Im
r
✓
sz
w
�
zw
rs
= rs(cos(✓ + �) + isin(✓ + �))zw
複素数の掛け算は、 「絶対値の積」×「偏角の和」になっていた
複素平面を UIView で作ってみよう
class ComplexPlane : UIView { ! var unit: CGFloat = 50.0 var scale: CGFloat = 1.0 var points: [String: Complex] = [:] var colors: [String: UIColor] = [:] subscript(name: String) -> (Complex!, UIColor!) { get { return (points[name], colors[name]) } set(value) { points[name] = value.0 colors[name] = value.1 } } override func drawRect(rect: CGRect) { … } }
let cplane = ComplexPlane(frame: …) cplane.scale = 2.0 !cplane["1"] = (1, nil) cplane["i"] = (i, nil) !let z = Complex(r: 2, θ: M_PI / 3) cplane["z"] = (z, UIColor.redColor()) !let w = z * z cplane["w"] = (w, UIColor.blueColor()) !cplane.setNeedsDisplay() cplane
DEMO : 動かしてみよう!
https://github.com/taketo1024/SwiftComplex
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
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
鉛直軸は |z|sin(arg(z)) としている
zw
w = f(z) = z2 + z + 1
は平面全体をちょうど2重に覆う写像だった!
まさに「神のクレープ」
y = x
2 + x+ 1
x
(放物線を y軸 に潰した形)
f
これを実数に制限したものが、 今まで y = x^2 + x + 1 だと思っていたもの。y = x
2 + x+ 1
あぁ、なんと不自由な…
w = z3 + z2 � 2z + 1
f(z) = anzn + ...+ a1z + a0
は平面全体をちょうどn重に覆う写像になる。一般に
特に 0 に移る z も n 個あることになるので…
代数学の基本定理
ちょうど n 個 の複素数解 を持つ!
anzn + ...+ a1z + a0 = 0n 次方程式 は、
「数」は複素数まで拡張されて完成を迎える。
今日伝えたかったこと
• 複素数は作れる、そして美しい。
• Swift だからこそキレイに作れた。
• 数学とプログラミングは同時に学ぶべき。
次回予告(?)
• e, π, i の饗宴 ~ Swift で学ぶオイラーの公式
• 無限遠を一点に ~ SceneKit で作るリーマン球面
e,⇡, i
Thanks!@taketo1024