本家 CRuby の Rational クラスは、バージョン 1.9 から require せずに使えるようになっていますが、mruby では本体に組み込まれておらず、該当するような mrbgem も見つからなかったので書いてみました。
mruby はかなり基本的な機能以外は徹底的に削ってあり、組み込まれた機能も ISO に準拠するような形で実装されています。
Ruby の ISO を確認したところ、Rational クラスは特に入ってないみたいでした。ということで、mruby-rational では挙動を CRuby 1.9.3 のリファレンスに合わせるように書いています。
mruby-rational を使うと、次のように有理数を用いた計算ができます。
a = Rational(1, 4)
=> (1/4)
b = Rational(2, 3)
=> (2/3)
a + b
=> (11/12)
(a + b).to_f
=> 0.91666666666667
x = Rational(1, 3) * 2
=> (2/3)
y = Rational(1, 2)
=> (1/2)
x * y
=> (1/3)
CRuby では rational.c として C で実装されていますが、mruby-rational は Ruby だけで実装しています。必要となるメソッドが Ruby だけで十分そうだった点、せっかくコンパクトな mruby の実装なのだから、gem のコードも簡潔に書きたかった点、などなどいろいろ理由はありますが、本当は自分の書いたコードにあまり自信を持ってないので、風通しをよくして他の方でもバグを見つけてもらいたいな、といった狙いもあります。
中で行なっている約分では、最大公約数を見つけるのにユークリッドの互除法を用いています。
既知の問題点(2015-12-18時点)
ある程度、CRuby の Rational と区別して切り分けして使う分には使えると思いますが、まだまだ互換性がありません。今日までのところ、計算上の精度まわりもチェックをしていませんのでこれから叩き上げたいところです。
Rational.initialize が外に出てる
Kernel.#Rational
からインスタンスを作成する際、Rational.convert
経由で普通に Rational.new
しています。 つまり外からも new
が叩けます。 CRuby だったらアクセサで隠して send
でぶっ叩いたり、caller
で呼出元によって例外を吐いたりと色々方法がありそうですが…。 もう少し勉強してなんとかならないか考えます。
ちなみに CRuby の rational.c では、rb_undef_method(CLASS_OF(rb_cRational), "new")
しておいて C 関数でインスタンス作っているようです。ずるい!
inline static VALUE
nurat_s_new_internal(VALUE klass, VALUE num, VALUE den)
{
NEWOBJ(obj, struct RRational);
OBJSETUP(obj, klass, T_RATIONAL);
obj->num = num;
obj->den = den;
return (VALUE)obj;
}
Rational.convert
も private
じゃなく形式的に CRuby と合わせているだけなので、利用者サイドの観点からすると不要なのかもしれません。
初期化時に投げられる引数の種類が少ない
個人的に Rational クラスに求めているものは、分子と分母のペアなのでさほど困りはしないのですが、mruby-rational では今のところ
Rational(2, 3) # => (2/3)
Rational(5) # => (5/1)
という作り方しかサポートしません。つまり、下記のように文字列を一つ渡す
Rational('1/3') # => (1/3)
Rational('0.33') # => (33/100)
ではエラーとなります。CRuby では、string_to_r_internal()
でガリガリやっているっぽいです。 また、CRuby では
Rational(0.3)
=> (5404319552844595/18014398509481984)
となる Float を一つだけ取る初期化も
Rational(0.3)
=> (5.4043195528446e+15/1.8014398509482e+16)
という具合にBIGNUM周りの違いが出ています。
Complex クラスとの整合性はとらない
CRuby では、Complex 型を取り扱う挙動も rational.c に含まれていますが、これはそのままでいいんじゃないかな、と思っています。
なんとなく方向性は分かってきたので、少しずつ手を入れていきたいと思います。