up:: 代数学
Swiftで代数学入門 〜 3.有理数を作ってみよう - Qiita
有理数を作ろう
有理数は整数全てを分数として分数の概念を追加したもの。
分数は分子と分母、二つの整数を使って表す数。
有理数体Qは二つの整数p,qを持つstructとして作る。
struct Q {
let p, q: Z
init(_ p: Z, _ q: Z) {
if q == 0 {
fatalError("denom: 0")
}
self.p = p
self.q = q
}
}
_
は引数ラベルの省略。swiftでは関数を呼び出す際、定義しておいた引数ラベルを付けて引数を入れなければ、関数を使うことが出来ない。
_
を使えば、この機能を無効化して引数順で引数を代入出来る。
しっかりさっき作った加法群を使っていく。0も忘れず弾くこと。
ついでに簡単に整数を作れるよう、分母を指定しない場合は1とみなすイニシャライザも追加。
struct Q {
...
init(_ n: Z) {
self.init(n, 1)
}
}
'''は省略。
このままだとただのstructで計算不可なので、演算を追加する。
まずはEquatable。
分数が等しいとは
Q(p1, q2) == Q(p2, q2)
なら (p1 == p2) && (q1 == q2)
やろ! は短絡的。「約分して等しい」も等しくする。そのために式を書き換え。
ドット積なら素因数分解とかも必要ないので、このまま実装する。
struct Q: Equatable {
...
}
func ==(a: Q, b: Q) -> Bool {
return a.p * b.q == a.q * b.p
}
ここで別の視点を使ってみる。このQを構成する二つの整数p,qを、平面上の格子点(p,q)として見てみる。
すると、分数として二つの格子点が等しいとは、原点から結んだ同一直線上に格子点があるという意味になる。
つまりこの考え方の中では、有理数を直線として表せる。そうするとQはこの直線をすべて集めたものととらえられる。
あるいはこの直線とy=1の交点がちょうどその有理数になるので、格子点に原点から光を当て、スクリーンy=1に映した像がQと思うこともできる。……後者の言い方をするなら光は逆では……?
数学的余談:
数学的には直積を同値関係で割った商集合として有理数体Qを作った、という扱いらしい。直積集合の要素を同値関係で分割する。同値関係はグループ分けの基準、分けられたグループが同値類。例えば、整数の集合Zと同値関係Rがあるとする。Rは、2つの整数が偶数または奇数である場合に同値であると定義する。この場合、Z×ZをRで割ると、4つの同値類が得られる。すなわち偶数-偶数、偶数-奇数、奇数-偶数、奇数-奇数。
今回の場合は二つの整数集合を直積にして新たな集合を作り、それを同値関係という条件で分割し、出てきた値を全て包括して有理数体Qと呼ぶ、ということらしい。
は左右の順序対が等しいこと(同値関係)を、は二つの条件が等しいことを表している。
四則演算の定義
まだ等しいことしか実装してないので、ここから演算を始める。
まずは足し算のために、分母共通化操作「通分」を定義する。
マシンパワーで一番手っ取り早く通分。
最後の結果の式を返すようにする。
func +(a: Q, b: Q) -> Q {
return Q(a.p * b.q + a.q * b.p, // 分子
a.q * b.q) // 分母
}
次、マイナス。
負の数にすればいい、つまり分子の符号を逆に。
prefix func -(a: Q) -> Q { return Q(-a.p, a.q) }
引き算もこれで行ける。頭いいな。
掛け算。これもまんま。
func *(a: Q, b: Q) -> Q { return Q(a.p * b.p, a.q * b.q) }
割り算。逆数を定義しとけばへーき。
struct Q: Field {
...
var inverse: Q {
return Q(q, p)
}
}
これにて等しいこと、及び四則演算を定義した有理数体Qが完成する。
追加
swiftの事情。型の暗黙キャストを実装する。
Int値から変換できるようにするIntegerLiteralConvertible
に準拠したエクステンションを作成。
extension Q: IntegerLiteralConvertible {
init(integerLiteral value: IntegerLiteralType) {
self.init(value)
}
}
init(integerLiteral value: IntegerLiteralType)
イニシャライザに対してどうするか、を定義する。今回はここでstruct Qの中にInt一個の時のイニシャライザを作ってあるので、それに渡す形にしとけばいい。
これで「型であるQに数値入れるとQ型の値を返してくれるようになる」だけでなく、式の中で暗黙的に変換して計算してくれるようになる。
let a: Q = 3
let b: Q = Q(3, 4)
a * b == Q(9, 4) // true
let a: Q = Q(3, 4)
a * 4 == 3 // true
ついで、print
を使うときの表記を整える。デバッグを楽に。
そのままだとstructとしてQ(p: 3, q: 4)
みたいに表記されるので、CustomStringConvertible
に準拠して整える。実装するのはdescription
プロパティ。
extension Q: CustomStringConvertible {
var description: String {
switch q {
case 1: return "\(p)"
case -1: return "\(-p)"
default: return "\(p)/\(q)"
}
}
}
qの値でpとqを使った文字列リテラルを条件分岐して返すだけ。
ここは単純に考えよう。成功すると分数っぽく表記してくれる。
let a = Q(3, 4)
print("a = \(a)") // a = 3/4
こうなると約分が欲しくなるが、それは次々回で。