math
Protocol
Generic
Swift
PONS

Swift - introducing Int2X, and PONS will be back

弾です。ご無沙汰しております。Swift 2から更新が止まっていた PONS を Swift 4 と Swift Package Manager 向けに作り直しているのですが、寄り道的にこんなパッケージを作成してみました。


Swift Integers are Now Protocol Oriented

PONSの目標は Swift の数値型を Protocol Oriented にすることでしたが、これはSE-0104の採用により実現しました。PONSの目標は達成されたのですが、皮肉にもそのおかげで PONS 自体は現在の Swift とは非互換になってしまいました。

しかしそれは想定の範囲。この業界ではよくあることです。しかしPONSが提供していた任意精度浮動小数点型(BigFloat)や複素数型Complexまで失われてしまったのはいささか残念だったので、やはりPONSを復活させようと決めたのですが、今度は方針を180度変えています。


  • 今まで単一パッケージ、単一リポジトリだったものを型ごとに分ける

  • 型どおしの依存性は Swift Package Manager で解決する

これこそが本来のパッケージのあるべき姿で、かつてのPONSがモノリシックだったのはプロトコル自体を自前で用意しオーケストレートしなければならないためのやむ得ぬ措置でした。

ここでも Protocol Oriented であることが活きてきます。モジュールが組み合わさることはプロトコルが保証してくれるのです。実際新PONSでは任意精度整数は自前ではなくattaswift/BigIntを採用していますが、これもBigIntが Swift 4 標準のSignedIntegerBinaryIntegerに準拠しているからで、それらプロトコルに準拠しているのであれば他のモジュールでもいいわけです。

とりあえず現在まで

任意精度浮動小数点型(BigFloat)と任意精度有理数型(BigRat)。ドキュメントがまだですがほぼ完成しております。内部でBigIntを使ってます。

複素数型(Complex)。PONSとは別に復活させました。Complex<Double>だけではなくComplex<BigFloat>とかも使えるのは実証済み。

区間演算型(interval arithmetic)。それ自体FloatingPointなので、この型をDoubleなどの代わりに使うだけで区間演算ができます。そしてこれもまたInterval<Double>だけではなくInterval<BigFloat>としても使えますし、さらにComplex<Interval<BigFloat>>としても使えることを確認しております。

この区間演算型にちょっと注目してみてください。これはFloatingPointを要素として持つFloatingPoint型です。つまりあるプロトコルに準拠する型を要素として持つ型がそのプロトコルに準拠するという再帰型プロトコル(recursive protocol)というのは可能なわけです。

そして実は、可能なだけではなく役にも立ちます。整数型でこれをやれば、倍幅の整数がいとも簡単に作れるのです。UInt64からUInt128UInt128からUInt256といった具合に。

Int2Xはまさにそれをやるためのパッケージです。

import Int2X

typealias U128 = UInt2X<UInt64> // Yes. That's it!
typealias I128 = Int2X<UInt64> // ditto for signed integers

実際には128から1024まであらかじめ以下のように定義してあります。

public typealias UInt128   = UInt2X<UInt64>

public typealias UInt256 = UInt2X<UInt128>
public typealias UInt512 = UInt2X<UInt256>
public typealias UInt1024 = UInt2X<UInt512>
public typealias Int128 = Int2X<UInt64>
public typealias Int256 = Int2X<UInt128>
public typealias Int512 = Int2X<UInt256>
public typealias Int1024 = Int2X<UInt512>

あらかじめ定義してあるのは1024までですが、これはAccelerateがサポートしているのが1024bitまでということと、それ以上大きくしてもBigIntより高速にならずメモリー効率も悪いということでそうしているだけで、当然ですがUInt1024UInt2048を作ることも問題なくできます。

 1> import Int2X 

2> typealias UInt2048 = UInt2X<UInt1024>
3> (1...300).map{UInt2048($0)}.reduce(UInt2048(1),*).toString(radix:16)
$R0: String = "26ca8439285c67e3b9e32731120f78b66f85612a4dd1df6c1d2c69a2d7685f85e75b82bf36e22dcb6ca5044f43fef0b3316049241c821e1ec1c63f95685931f87152ced4befc5ddf7b76719a636f68e7054bf28b57d54d40975ab14d65900681c49044d1725031edbf3c1c5c595b871d6864c371b0cc54ef19d42ad02833bcaf5fb1671b523a143b132c7e1971f7bf5ca77505c960f14b330e6c90dc2539431329ef78a1e9f26b2ead7d28a622e6b586bcee22bd0a495442c6a1235588988252cbd4d36975560fb8e7e5c8cf06f29aeb68659c5cb4cf8d011375b00000000000000000000000000000000000000000000000000000000000000000000000000"

で、前述の通り Accelerate が 使える macOS と iOS では使うようにしてあります(手動でオンオフも可能)。

予定としては、新PONSはこれらを統合する「スーパーモジュール」として生まれ変わる予定です。といっても実際Package.swiftでこれらを引っ張ってきてまとめるだけなのでそちらは大したことはないのですが。

とりあえず以上です。

Enjoy!

Dan the Protocol-Oriented Swift Programmer