前回 と前々回 ではそれぞれC(libc)とObjective-C(Foundation)をSwiftから活用してみましたが、どちらもiOS/OS X標準のAPIへのアクセスでした。今回はサードパーティのC/Objective-Cプロジェクトを、Swiftから活用する方法を実例をもとに見ていきます。
swift-gmpint
ご存じのとおり、Swiftにおける整数=Int
は、C/Objective-C/C++のint
同様、1ワード。64bitプラットフォームなら64bit、32bitプラットフォームなら32bitです。しかしRubyやPythonやHaskellなど、昨今の言語では組み込みの整数を任意精度にする事例が増えています。Swiftでも任意精度の整数を扱えるようにしたいというのは自然の欲求というものでしょう。任意精度整数が組み込みでない言語でその欲求を叶える方法としては、次の2通りが考えられます。
A.100%自分で実装
B.既存のライブラリを活用
たとえばJavaScriptでは事実上A.の方法しか採れませんが、SwiftならプランB.があります。Mathematicaでも採用されている定番の任意精度整数ライブラリ、GMP を、そのままSwiftから使えるようにしてしまえばいいのです。
そんなわけで作ったのがswift-gmpint です。MacPorts でインストールしたGMPを、Swiftから使えるようにします。
Homebrew 派の皆さん、ごめんなさい。ただし未確認ではありますが、本記事中の/opt/local
を/usr/local
にすればもしかして動くかもしれません。fork welcome!
Quick Start
前口上は抜きにして早速試してみたいという読者は、次のようにしてください。
標準出力を確認してみてください。2**1024が179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216
であることなどが確認できます。さらにmain.swift
をあれこれ書き換えてみて、任意精度整数演算を楽しんでみてください。
普通のプロジェクトとの違い
それでは、実際のプロジェクトをみてみましょう。まずはプロジェクトファイル(図1 )から。
図1 通常のSwiftプロジェクトとの違い
通常のプロジェクトとの一番の違いは、Cフラグとリンクオプション。-lgmp
と-I/opt/local/include -L/opt/local/lib
が追加してあります。ほかは通常のSwiftプロジェクトと変わりません。
次にプロジェクト中のファイルをみてみましょう。
main.swift
mpz.swift
gmp-int-Bridging-Header.h
mpz.c
特徴的なのが、mpz.c
とgmpint-Bridging-Header.h
。[ Using Swift with Cocoa and Objective-C]に出てくる三角形の上2つがこれに相当します(図2 ) 。前々回と前回のプロジェクトではXcode組み込みのファウンデーションを利用していたためこれらは不要でしたが、今回はGMPというサードパーティライブラリを使うため必要となります。
図2 [ Using Swift with Cocoa and Objective-C]
ちなみにgmp-int-Bridging-Header.h
は、Xcodeにひな型を生成させることもできますが、*
プロジェクト名*-Bridging-Header.h
という名前をつけてプロジェクトに手で追加してもOKです(図3 ) 。
図3 Bridging-Header.hの追加
それでは、gmp-int-Bridging-Header.h
を見てみましょう(リスト1 ) 。
リスト1 gmp-int-Bridging-Header.h
#include <gmp.h>
void gmpint_seti(mpz_t *op, long i);
void gmpint_sets(mpz_t *op, const char *str, int base);
void gmpint_unset(mpz_t *op);
size_t gmpint_strlen(mpz_t *op, int base);
char *gmpint2str(mpz_t *op, int base);
int gmpint_fits_int(mpz_t *op);
long gmpint2int(mpz_t *op);
int gmpint_cmp(mpz_t *op, mpz_t *op2);
void gmpint_negz(mpz_t *rop, mpz_t *op);
void gmpint_absz(mpz_t *rop, mpz_t *op);
void gmpint_lshift(mpz_t *rop, mpz_t *op, mp_bitcnt_t bits);
void gmpint_rshift(mpz_t *rop, mpz_t *op, mp_bitcnt_t bits);
void gmpint_addz(mpz_t *rop, mpz_t *op, mpz_t *op2);
void gmpint_subz(mpz_t *rop, mpz_t *op, mpz_t *op2);
void gmpint_mulz(mpz_t *rop, mpz_t *op, mpz_t *op2);
void gmpint_divmodz(mpz_t *r, mpz_t *q, mpz_t *op, mpz_t *op2);
void gmpint_powui(mpz_t *rop, mpz_t *op, unsigned long exp);
void gmpint_powmodz(mpz_t *rop, mpz_t *op, mpz_t *exp, mpz_t *mod);
これらの関数名が、Swiftでそのまま使えます。ただしそのまま使えるのは関数名までで、型の名前は読み替えが必要になります。たとえばCint
はSwift上でInt32
になりますし、char*
はUnsafePointer<Int8>
になります。Cの型名とSwiftの型名の対応表は[Using Swift withCocoa and Objective-C]にも載っていますが、実際にはXcodeのコード補完でいつでも確認できるのでそれほど気にする必要はないでしょう。IDE万歳といったところです。
そしてこれらの関数がmpz.c
に実装されているのですが、関数を1つだけ紹介しておきます。
void gmpint_seti(mpz_t *op, int i) {
mpz_init_set_si(*op, i);
}
見てのとおり、名前を変えているだけです。Cに慣れたプログラマであれば「マクロでいいじゃん」と言いそうですが、コード補完のことを考えるとマクロは用いないほうがよいでしょう。
もうこの時点で、関数だけであればGMPの機能をSwiftから呼び出せるようになっているのですが、せっかくSwiftを使っているのですから、Swift用の型と演算子を用意して、Swift的に使えるようにするべきです。それを実装しているのがmpz.swift
です。まずは型をみてみましょう(リスト2 ) 。
リスト2 mpz.swift抜粋(その1)
class GMPInt {
private var mpz = mpz_t()
init(){ gmpint_seti(&mpz, 0)}
init(_ mpz:mpz_t) { self.mpz = mpz }
init(_ s:String, base:Int=10){
s.withCString {
gmpint_sets(&self.mpz, $0, Int32(base))
}
}
// to work around the difference between
// GMP's 32-bit int and OS X's 64-bit int,
// we use string even for ints
convenience init(_ i:Int) { self.init(String(i)) }
deinit {
gmpint_unset(&mpz)
}
func toInt() -> Int? {
return gmpint_fits_int(&mpz) == 0 ?
nil : Int(gmpint2int(&mpz))
}
var asInt: Int? {
return self.toInt()
}
}
まず目につくのが、init()
が2種類あること。GMPInt(42)
もGMP("42")
も受け付けます。文字列からの初期化が必要なのは、GMPInt("012345678901234567890123456789012345678901")
のように、固定整数には収まらない整数のことを考えれば自明というより、むしろ基本と言ってもいいでしょう。実際固定整数からの初期化init(_ i:Int)
の実装は、わざわざString(i)
で文字列化しています。コメントにあるとおり、効率を犠牲にして互換性を確保しています。
次の特徴は、deinit()
の存在。GMPはCライブラリ[1] だけあって、メモリ管理が全自動のSwiftとは異なり、メモリの解放を手で行わなければなりません。deinit()
はまさにこのような場合のために存在します。Swiftがメモリを解放する際、このメソッドがあれば呼び出されるので、そのタイミングでC側のメモリも解放するようにすればよいわけです。Perlをご存じの読者は、DESTROY
相当のメソッドであると覚えておくとよいかもしれません。余談ですが、SwiftもPerlもメモリ管理はリンクカウント方式で、オブジェクトの解放はリンクカウントが0になった段階で行われます。
型定義ができたところで、メソッドと演算子を追加していきます(リスト3 ) 。
リスト3 mpz.swift抜粋(その2)
extension GMPInt: Printable {
func toString(base:Int=10)->String {
let cstr = gmpint2str(&mpz, Int32(base))
let result = String.fromCString(cstr)
free(cstr)
return result!
}
var description:String { return toString() }
}
リスト3でふつうにprintln()
できるようになり……。
extension GMPInt: Equatable, Comparable {}
func <(lhs:GMPInt, rhs:GMPInt)->Bool {
return gmpint_cmp(&lhs.mpz, &rhs.mpz) < 0
} func ==(lhs:GMPInt, rhs:GMPInt)->Bool {
return gmpint_cmp(&lhs.mpz, &rhs.mpz) == 0
}
これで等号不等号が使えるようになりました。
あとは、演算子を定義していくだけです。
/// unary +
prefix func +(op:GMPInt) -> GMPInt { return op }
/// binary +
func +(lhs:GMPInt, rhs:GMPInt) -> GMPInt {
var rop = GMPInt()
gmpint_addz(&rop.mpz, &lhs.mpz, &rhs.mpz)
return rop
}
func +(lhs:GMPInt, rhs:Int) -> GMPInt {
return lhs + GMPInt(rhs)
}
func +(lhs:Int, rhs:GMPInt) -> GMPInt {
return GMPInt(lhs) + rhs
}
見てのとおり、二項演算子は片方がInt
な場合も受け付けるようにして、なるべくシームレスに演算できるようにしておきましょう。
まとめ
というわけで任意精度整数がSwiftでも使えるようになったのですが、ここで一から実装した例と今回のGMPを活用した例を比較してみましょう(図4、図5 ) 。
図4 swift-gmpintの行数
23 130 981 gmpint/gmpint-Bridging-Header.h
41 117 867 gmpint/main.swift
64 220 1632 gmpint/mpz.c
233 827 5800 gmpint/mpz.swift
361 1294 9280 total
図5 bigint.jsの行数
560 2368 15429 ../js-math-bigint/bigint.js
bigint.js
はJavaScriptで必要最低限の任意精度整数演算を実装したものですが、JavaScriptだけあって当然演算子などは使えず、たとえば2**1024
を計算したければ(new Math.BigInt(2)).mul(1024)
などとしなければならないのに対し、SwiftであればGMPInt(2) **1024
で事足りてしまいます。どちらが楽かはご覧のとおりです。
第1特集
MySQL アプリ開発者の必修5科目
不意なトラブルに困らないためのRDB基礎知識
第2特集
「知りたい」「 使いたい」「 発信したい」をかなえる
OSSソースコードリーディングのススメ
特別企画
企業のシステムを支えるOSとエコシステムの全貌
[特別企画]Red Hat Enterprise Linux 9最新ガイド
短期連載
今さら聞けないSSH
[前編]リモートログインとコマンドの実行
短期連載
MySQLで学ぶ文字コード
[最終回]文字コードのハマりどころTips集
短期連載
新生「Ansible」徹底解説
[4]Playbookの実行環境(基礎編)