【Swift4.0】16進数文字列をbigintに変換【JavaScriptCore】

ある案件で16進数文字列をbigintに変換する必要に迫られました。Swiftでbigintを扱えるライブラリはあるにはあるものの、16進数文字列から変換できるものはOpenSSLを使用するものだったりしてインストールが少々メンドくさそう…何かいい手はないかと考えた結果、とある方の書いたJavascriptをJavaScriptCore経由で利用させていただくことにしました。

#プロジェクト導入手順

## 1. hex2decをgithubからダウンロード

ダウンロードしたindex.htmlを開いてみると分かりますがWebツールです。これのjs部分を利用させてもらいます。

[lokielse/hex2dec](https://github.com/lokielse/hex2dec)
<img width="928" alt="hex1.png" src="https://qiita-image-store.s3.amazonaws.com/0/151314/5f8ff762-c95f-58ec-1043-89b07761b5ea.png">

## 2. jsファイルを作成
上記のindex.htmlをエディタで開き、ソースからhtmlタグとjsの実行部分を除きます。jsの定義部分のみとなったファイルを今回は `bigint.js`と命名して保存しました。

bigint.js

```swift
/**
 * A function for converting hex <-> dec w/o loss of precision.
 *
 * The problem is that parseInt("0x12345...") isn't precise enough to convert
 * 64-bit integers correctly.
 *
 * Internally, this uses arrays to encode decimal digits starting with the least
 * significant:
 * 8 = [8]
 * 16 = [6, 1]
 * 1024 = [4, 2, 0, 1]
 */

// Adds two arrays for the given base (10 or 16), returning the result.
// This turns out to be the only "primitive" operation we need.
function add(x, y, base) {
    var z = []
    var n = Math.max(x.length, y.length)
    var carry = 0
    var i = 0
    while (i < n || carry) {
        var xi = i < x.length ? x[i] : 0
        var yi = i < y.length ? y[i] : 0
        var zi = carry + xi + yi
        z.push(zi % base)
        carry = Math.floor(zi / base)
        i++
    }
    return z
}

// Returns a*x, where x is an array of decimal digits and a is an ordinary
// JavaScript number. base is the number base of the array x.
function multiplyByNumber(num, x, base) {
    if (num < 0) return null
    if (num == 0) return []

    var result = []
    var power = x
    while (true) {
        if (num & 1) {
            result = add(result, power, base)
        }
        num = num >> 1
        if (num === 0) break
        power = add(power, power, base)
    }

    return result
}

function parseToDigitsArray(str, base) {
    var digits = str.split('')
    var ary = []
    for (var i = digits.length - 1; i >= 0; i--) {
        var n = parseInt(digits[i], base)
        if (isNaN(n)) return null
        ary.push(n)
    }
    return ary
}

function convertBase(str, fromBase, toBase) {
    var digits = parseToDigitsArray(str, fromBase)
    if (digits === null) return null

    var outArray = []
    var power = [1]
    for (var i = 0; i < digits.length; i++) {
        // invariant: at this point, fromBase^i = power
        if (digits[i]) {
            outArray = add(outArray, multiplyByNumber(digits[i], power, toBase), toBase)
        }
        power = multiplyByNumber(fromBase, power, toBase)
    }

    var out = ''
    for (var i = outArray.length - 1; i >= 0; i--) {
        out += outArray[i].toString(toBase)
    }
    return out
}

function decToHex(decStr) {
    var hex = convertBase(decStr, 10, 16)
    return hex ? '0x' + hex : null
}

function hexToDec(hexStr) {
    if (hexStr.substring(0, 2) === '0x') hexStr = hexStr.substring(2)
    hexStr = hexStr.toLowerCase()
    return convertBase(hexStr, 16, 10)
}
```

## 3. jsファイルをプロジェクトに追加
ターゲット選択 -> Build Phase -> Copy Bundle Resources と辿り追加します。
<img width="1114" alt="hex2.png" src="https://qiita-image-store.s3.amazonaws.com/0/151314/418c9a63-a59b-5645-12dd-ccaa337aa045.png">


## 4. JavaScriptCore.frameworkをプロジェクトに追加
ターゲット選択 -> General -> Linked Frameworks and Libraries と辿り追加します。
<img width="1159" alt="hex3.png" src="https://qiita-image-store.s3.amazonaws.com/0/151314/95b367d8-d42a-dae5-8acc-e3c987cd8eed.png">

## 5. 使う
準備完了です。16進数文字列をbigintの10進数文字列に変換するサンプルコードです。

冒頭で`JavaScriptCore.framework`をインポートします。

```swift
import JavaScriptCore
```
```swift

        // bigint.jsを文字列バッファ上に取得

        let bundle = Bundle.main.path(forResource: "bigint", ofType: "js")
        var buff = try! String(contentsOfFile: bundle!, encoding: String.Encoding.utf8)
        
        let hexStr = "0x15bcebe43a644c5400000000000" // for test

        // 定義バッファに「var ret = hexToDec('0x15bcebe43a644c5400000000000');」をアペンド
        buff += "var ret = hexToDec('" + hexStr + "');"
        
        // jsコンテキスト生成
        let context = JSContext()!

        // 文字列をコードとして評価 (js's eval)
        context.evaluateScript(buff)

        // 評価後のretを取得して文字列に変換 （JSValue -> String）
        let value = context.objectForKeyedSubscript("ret").toString()

        print("biginteger string => \(value!)")
        // デバッグコンソール表示結果: biginteger string => 27556156319844318314496000000000

```
bigint.jsを読み込んだバッファの末尾に実行コード文字列を追加しています。このサンプルでは16進数文字列をハードコーディングしていますが、実用時は変数`hexStr`をパラメタにとる関数を作成することになるでしょう。計算が必要なら10進数文字列からbigintをインスタンスできるライブラリはいくつか公開されていますのでそれらを利用すればよいと思います。




クラスメソッド化

```swift
    class func hexStringToDecimalString(hex:String) -> String {

        var value = ""
        
        do {
            let bundle = Bundle.main.path(forResource: "bigint", ofType: "js")
            var buff = try String(contentsOfFile: bundle!, encoding: String.Encoding.utf8)
            buff += "var ret = hexToDec('" + hex + "');"
            
            let context = JSContext()!
            context.evaluateScript(buff)
            value = context.objectForKeyedSubscript("ret").toString()
            
        } catch {
            //error
        }
        
        return value
    }
```

・・・読み込んだ定義部分に実行コードをアペンドするってのがちょっとしょっぱい感じですw 
Swift/JSのインターフェースをカッコよく実装したいなら小飼さんの[SwiftでJavaScript](https://qiita.com/dankogai/items/052a3ad6f32d114a33fc)の記事が参考になります。

ちなみに、bigでない普通のIntであればこれでOK。

```swift
Int("2a", radix: 16)! // => 42
```

## 補足　：bigintを扱えるSwiftライブラリ

[BigInt](https://github.com/attaswift/BigInt) 
[Swift-Big-Integer](https://github.com/mkrd/Swift-Big-Integer)