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) やろ! は短絡的。「約分して等しい」も等しくする。そのために式を書き換え

{\begin{eqnarray} \frac{p}{q} = \frac{p'}{q'} \Leftrightarrow p \cdot q' = q \cdot p' \end{eqnarray} }

ドット積なら素因数分解とかも必要ないので、このまま実装する。

struct Q: Equatable {
    ...
}
 
func ==(a: Q, b: Q) -> Bool {
    return a.p * b.q == a.q * b.p
}

ここで別の視点を使ってみる。このQを構成する二つの整数p,qを、平面上の格子点(p,q)として見てみる。
すると、分数として二つの格子点が等しいとは、原点から結んだ同一直線上に格子点があるという意味になる。

https://camo.qiitausercontent.com/60fa7d41301e4629b2a6eb7ab821fdd4aecbe1ba/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f383134362f33353236623038642d616335662d633563342d396363352d3864396635353637373838622e706e67

つまりこの考え方の中では、有理数を直線として表せる。そうすると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

こうなると約分が欲しくなるが、それは次々回で。