ruby本体にメソッドを足してみる

Rubyオープンクラスというのは、メソッドを勝手に足していいってことらしいので、RubyレベルとCレベルの2通りの方法でやってみた。Cのほうは破壊検査的な学習法かも。

Numeric#dividable?というメソッドがあってもいいだろうと思って、

class Numeric
  def dividable?(n)
    self % n == 0
  end
end

ary = 
  (1..100).map do |i|
  case
  when i.dividable?(15) then
    "FizzBuzz"
  when i.dividable?(3) then
    "Fizz"
  when i.dividable?(5) then
    "Buzz"
  else
    i
  end
end

# puts ary

のように書いた。このメソッド定義を、numeric.cにnum_dividableという関数で加えてみた。

--- a/numeric.c
+++ b/numeric.c
@@ -361,6 +361,19 @@ num_modulo(VALUE x, VALUE y)
     return rb_funcall(x, '%', 1, y);
 }
 
+// experimental
+
+static VALUE
+num_dividable(VALUE x, VALUE y)
+{
+  int m = FIX2INT(rb_funcall(x, '%', 1, y));
+  if (m == 0) {
+    return Qtrue;
+  } else {
+    return Qfalse;
+  }
+}
+
 /*
  *  call-seq:
  *     num.remainder(numeric)    => result
@@ -3118,6 +3131,7 @@ Init_Numeric(void)
     rb_define_method(rb_cNumeric, "div", num_div, 1);
     rb_define_method(rb_cNumeric, "divmod", num_divmod, 1);
     rb_define_method(rb_cNumeric, "modulo", num_modulo, 1);
+    rb_define_method(rb_cNumeric, "dividable?", num_dividable, 1);
     rb_define_method(rb_cNumeric, "remainder", num_remainder, 1);
     rb_define_method(rb_cNumeric, "abs", num_abs, 0);
     rb_define_method(rb_cNumeric, "magnitude", num_abs, 0);

makeしたら、

$ ./ruby -e 'puts 10.methods'  |grep -i div
div
divmod
fdiv
dividable?
$

とか、

10.dividable?(3)
=>false
10.dividable?(2)
=>true
10.dividable?(4)
=>false
10.dividable?(5)
=>true

とかなって、いちおう動いている。FizzBuzzの上限を100じゃなくて500万ぐらいにして計測したら、

$ time ./ruby fizz.rb 

real	0m4.498s
user	0m4.324s
sys	0m0.104s

$ time ./ruby fizz2.rb 

real	0m0.572s
user	0m0.536s
sys	0m0.020s

と、速いような気もする。しかし、ふつうに「割ってゼロか?」と書いたものに比べると、むしろ遅くなっている。

require 'benchmark'

n = 1000000

Benchmark.bm do |x|

  x.report {
    (1..n).each do |i|
      if ((i % 3) == 0) then "Fizz" end
      if ((i % 5) == 0) then "Buzz" end
    end
  }

  x.report {
    (1..n).each do |i|
      if i.dividable?(3) then "Fizz" end
      if i.dividable?(5) then "Buzz" end
    end
  }

end
$ ./ruby fizz_bench.rb 
      user     system      total        real
  0.360000   0.000000   0.360000 (  0.381753)
  0.820000   0.000000   0.820000 (  0.844860)

Ruby 1.9では算術命令はメソッド呼ばずにやる最適化をしているから、メソッドを呼ぶのより速いってことかしら。