書いて覚えるSwift入門

第6回 サードパーティC/Objective-Cプロジェクトの利用

この記事を読むのに必要な時間:およそ 4 分

そしてこれらの関数が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な場合も受け付けるようにして,なるべくシームレスに演算できるようにしておきましょう。

注1)
厳密にはC++ライブラリも含みますが,C++とのSwift連携はCより少し難しいのです。

まとめ

というわけで任意精度整数が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で事足りてしまいます。どちらが楽かはご覧のとおりです。

Software Design

本誌最新号をチェック!
Software Design 2019年10月号

2019年9月18日発売
B5判/176ページ
定価(本体1,220円+税)

  • 第1特集
    ⁠速い⁠Webアプリケーションの作り方[フロントエンド編]
    レンダリングのしくみからHTML/CSS/JavaScriptの書き方まで
  • 第2特集
    わかりやすい絵文字の講座
    絵文字をきっかけに振り返る文字コードの歴史
  • 特別企画
    快適な朝を創りあげる
    Ejectコマンド工作――職人の朝を支える技術の巻

著者プロフィール

小飼弾(こがいだん)

1969年生まれ,東京都出身。元ライブドア取締役の肩書きよりも,最近はPokemon GOのガチトレーナーのほうが有名になりつつある……かもしれない永遠のエンジニアオヤジ。

活躍の場はIT業界だけでなく,サブカルからアカデミックまで多方面にわたり,ネットからの情報発信は気の向くまま毎日毎秒! https://twitter.com/dankogai,ニコニコチャンネルは,http://ch.nicovideo.jp/dankogai,blogはhttp://blog.livedoor.jp/dankogai/

当社刊行書籍は『小飼弾のアルファギークに逢ってきた』『小飼弾のコードなエッセイ』など。他にも著書多数。