Ruby 拡張ライブラリで mpfr_t をラップする

ruby-mpfr は mpfr_t 型をラップして Ruby の MPFR オブジェクトにしている。 Ruby の拡張ライブラリを書こうとしたときにあまり情報がなかったので ruby-mpfr を作ったときの方法を忘れないようにまとめておく。

拡張ライブラリの作成方法

Ruby の拡張ライブラリの作成方法については、 まずは README.EXT.ja を読む。 ただ、拡張ライブラリを書いていると README.EXT.ja に載っていない関数を使いたくなるので、 ruby.h を見て使えそうな関数を探すことになる。 どこまで使って良いのかわからないが。

mpfr_t を調べて MPFR 型を定義する

ヘッダファイル mpfr.h を見ると

/* Definition of the main structure */
typedef struct {
  mpfr_prec_t  _mpfr_prec;
  mpfr_sign_t  _mpfr_sign;
  mpfr_exp_t   _mpfr_exp;
  mp_limb_t   *_mpfr_d;
} __mpfr_struct;

typedef __mpfr_struct mpfr_t[1];

なっている。 mpfr_t の実体は __mpfr_struct だということがわかるので ruby_mpfr.h で次のようにする。

typedef __mpfr_struct MPFR;

MPFR 型を __mpfr_struct にして Data_Make_Struct で Ruby オブジェクトを作成する マクロ r_mpfr_make_struct を定義する。

#define r_mpfr_make_struct(ruby_var, c_var) \
  { ruby_var = Data_Make_Struct(r_mpfr_class, MPFR, 0, r_mpfr_free, c_var); }

また、Ruby オブジェクトから MPFR 型を取り出す r_mpfr_get_struct も定義する。

#define r_mpfr_get_struct(c_var, ruby_var) \
  { Data_Get_Struct(ruby_var, MPFR, c_var); }

Ruby のメソッドに対応する関数を定義する

ruby_mpfr.c の中では MPFR 型を使ってさまざまな関数を定義している。 mpfr_t は __mpfr_struct のポインタであるので、 mpfr_t を引数とする関数に対しては (MPFR *) を渡す。

static VALUE r_mpfr_get_prec(VALUE self)
{
  MPFR *ptr_self;
  r_mpfr_get_struct(ptr_self, self);
  return INT2NUM((int)mpfr_get_prec(ptr_self));
}

self は Ruby では MPFR オブジェクト(Init_mpfr 関数は後で説明)なので r_mpfr_get_struct で (MPFR *) を取り出す。 ptr_self は __mpfr_struct のポインタとなるので mpfr_get_prec 関数の引数にすることができる。

MPFR オブジェクトの allocation

C言語では mpfr_t 型の変数は

mpfr_t a;
mpfr_init(a);
mpfr_set_si(a, 10, MPFR_RNDN);
mpfr_add(a, a, a, MPFR_RNDN);

のように使用するので、mpfr_t をラップした Ruby オブジェクトも どこかで mpfr_init で初期化するのと mpfr_set などで値を設定する必要がある。

Ruby のオブジェクトは rb_define_alloc_func に指定された関数で メモリを確保する。 また、rb_define_private_method で initialize メソッドを定め、 そこでオブジェクトの値を定める。 rb_define_alloc_func 用に r_mpfr_alloc という関数を作る。

static VALUE r_mpfr_alloc(VALUE self)
{
  MPFR *ptr;
  r_mpfr_make_struct(self, ptr);
  return self;
}

この関数は、ptr に r_mpfr_make_struct でメモリを確保して Ruby のオブジェクトを self に代入する。

initialize メソッド用に次の関数を定義する。 引数として精度を受け取り、その値を利用して mpfr_init で初期化し, mpfr_set で値を設定する(詳しくは ruby-mpfr のソースを参照)。

static VALUE r_mpfr_initialize(int argc, VALUE *argv, VALUE self)
{
  MPFR *ptr;
  r_mpfr_get_struct(ptr, self);
  switch(argc){
  case 0:
    mpfr_init(ptr);
    mpfr_set_nan(ptr);
    break;
  case 1:
    ...
  }
  return Qtrue;
}

MPFR オブジェクトの開放

Data_Make_Struct でメモリを開放するための関数 r_mpfr_free を指定してある。 mpfr_init してあるので mpfr_clear する必要がある。 また、そもそも (MPFR *) にメモリを確保してあるので free しなければならない。

void r_mpfr_free(void *ptr)
{
  mpfr_clear(ptr);
  free(ptr);
}

Init_mpfr()

ライブラリがロードされたときに実行される Init_mpfr 関数を定義する。 Ruby の MPFR クラスとしてグローバル変数 r_mpfr_class を用意して、 上で説明した関数を Ruby のメソッドと関連づける。

void Init_mpfr(){
  r_mpfr_class = rb_define_class("MPFR", rb_cNumeric);
  rb_define_alloc_func(r_mpfr_class, r_mpfr_alloc);
  rb_define_private_method(r_mpfr_class, "initialize", r_mpfr_initialize, -1);
  
  rb_define_method(r_mpfr_class, "get_prec", r_mpfr_get_prec, 0);
  ...
}

これで mpfr_t 型を Ruby のオブジェクトとしてラップできた。

Tags of current page

, , , ,