原因判明(2012年5月17日)
Ruby 1.9.3のパッチのどこかの段階でZeroDivisionErrorがでる基準が変わったみたい。
% ruby -v ruby 1.9.3p194 (2012-04-20 revision 35410) [i686-linux] % irb 1.9.3p194 :001 > 3.0%0.0 ZeroDivisionError: divided by 0 from (irb):1 from /usr/local/rvm/rubies/ruby-1.9.3-p194/bin/irb:16:in `<main>'
% ruby -v ruby 1.9.3p0 (2011-10-30 revision 33570) [i486-linux] % irb irb(main):001:0> 3.0%0.0 => NaN
これはよく知られている話らしい。
また、0による割り算はIntergerクラスではエラーとなりますが、FloatクラスではInfinity(無限大)やNaN(Not a Number)を返します。これらの値を使った演算はInfinityかNaNにしかなりません。入力をそのまま使って演算する場合など、0で割り算を行う可能性がある場合は注意してください。
(たのしいRuby 第3版, 第10章 数値(Numeric)クラス p. 177より)
このため、いままでは以下の判定が通っていたけど、例外が飛ぶようになったので動かなくなったみたい。
label = if (@spread.to_f % @marker_count.to_f == 0) || !@y_axis_increment.nil?
教訓としては、ゼロ割まわりの値を利用して判定を組んではいけないということか。
解決法
usr/local/rvm/gems/ruby-1.9.3-p194@rails3/gems/gruff-0.3.6/lib/gruff/base.rb:1067のlabelというメソッドを以下のように書き換えればよい。
def label(value) if !@y_axis_increment.nil? label = value.to_i.to_s elsif !@marker_count.nil? && @spread.to_f % @marker_count.to_f == 0 label = value.to_i.to_s elsif @spread > 10.0 label = sprintf("%0i", value) elsif @spread >= 3.0 label = sprintf("%0.2f", value) else label = value.to_s end parts = label.split('.') parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{THOUSAND_SEPARATOR}") parts.join('.') end
環境
Ubuntu 12.04 % ruby -v ruby 1.9.3p194 (2012-04-20 revision 35410) [i686-linux] % gem list | grep rmagick rmagick (2.13.1) % gem list | grep gruff gruff (0.3.6) % dpkg -l | grep magick ii imagemagick 8:6.6.9.7-5ubuntu3.1 image manipulation programs ii imagemagick-common 8:6.6.9.7-5ubuntu3.1 image manipulation programs -- infrastructure rc libmagick++3 8:6.6.0.4-3ubuntu1.1 object-oriented C++ interface to ImageMagick ii libmagick++4 8:6.6.9.7-5ubuntu3.1 object-oriented C++ interface to ImageMagick ii libmagickcore-dev 8:6.6.9.7-5ubuntu3.1 low-level image manipulation library - development files rc libmagickcore3 8:6.6.0.4-3ubuntu1.1 low-level image manipulation library ii libmagickcore4 8:6.6.9.7-5ubuntu3.1 low-level image manipulation library ii libmagickcore4-extra 8:6.6.9.7-5ubuntu3.1 low-level image manipulation library - extra codecs ii libmagickwand-dev 8:6.6.9.7-5ubuntu3.1 image manipulation library - development files rc libmagickwand3 8:6.6.0.4-3ubuntu1.1 image manipulation library ii libmagickwand4 8:6.6.9.7-5ubuntu3.1 image manipulation library ii librmagick-ruby 2.13.1-5build1 Transitional package for ruby-imagemagick ii perlmagick 8:6.6.9.7-5ubuntu3.1 Perl interface to the ImageMagick graphics routines ii ruby-rmagick 2.13.1-5build1 ImageMagick API for Ruby (documentation)
状況
今まで動いていたのだけど、Ruby 1.9系に切り替えたあたりから動かなくなった(?)。Gruff Graphs for RubyのCode Sampleをそのままコピーし、実行すると次のエラーがでる。
% ruby test_gruff.rb /usr/local/rvm/gems/ruby-1.9.3-p194@rails3/gems/gruff-0.3.6/lib/gruff/base.rb:1067:in `label': divided by 0 (ZeroDivisionError) from /usr/local/rvm/gems/ruby-1.9.3-p194@rails3/gems/gruff-0.3.6/lib/gruff/base.rb:590:in `setup_graph_measurements' from /usr/local/rvm/gems/ruby-1.9.3-p194@rails3/gems/gruff-0.3.6/lib/gruff/base.rb:532:in `setup_drawing' from /usr/local/rvm/gems/ruby-1.9.3-p194@rails3/gems/gruff-0.3.6/lib/gruff/base.rb:508:in `draw' from /usr/local/rvm/gems/ruby-1.9.3-p194@rails3/gems/gruff-0.3.6/lib/gruff/line.rb:53:in `draw' from /usr/local/rvm/gems/ruby-1.9.3-p194@rails3/gems/gruff-0.3.6/lib/gruff/base.rb:487:in `write' from test_gruff.rb:14:in `<main>'
該当コードをみてみると draw()というメソッドから呼び出されるlabel()というメソッドがありこいつの以下の部分が問題らしい。
label = if (@spread.to_f % @marker_count.to_f == 0) || !@y_axis_increment.nil?
どうも@marker_countが怪しい。以下のように puts を入れて確認してみる。
puts "debug marker_count = #{@marker_count.class}" label = if (@spread.to_f % @marker_count.to_f == 0) || !@y_axis_increment.nil?
すると
debug marker_count = NilClass
とでる。@marker_countは、initialize_ivars()で初期化されている。
def initialize_ivars # Internal for calculations @raw_columns = 800.0 @raw_rows = 800.0 * (@rows/@columns) @column_count = 0 @marker_count = nil @maximum_value = @minimum_value = nil @has_data = false @data = Array.new @labels = Hash.new @labels_seen = Hash.new @sort = true @title = nil 〜以下略〜
このあと、draw_line_markers()で値がいれられている。label()もdraw_line_markers()で呼び出されているので、そちらの場合は問題なのだけど、がdraw_line_markers()よりも前に呼び出されているsetup_graph_measurementsの中でも呼び出されている。こちらで呼び出された場合 @marker_count は空なので、ゼロ割の例外が飛ぶ。
gruff-0.3.6は2009年から更新されていないので、これまではこれでもちゃんと動いていた。何が変わってエラーがでるようになったのだろう?とりあえず、ここまで。
追記
不思議なことにこちらの環境だと同じソースでもきっちり動く。なぜ?
Debian GNU/Linux Testing % ruby -v ruby 1.9.3p0 (2011-10-30 revision 33570) [i486-linux] % gem list | grep rmagick rmagick (2.13.1) % gem list | grep gruff gruff (0.3.6) % dpkg -l | grep magick ii imagemagick 8:6.7.4.0-5 image manipulation programs ii imagemagick-common 8:6.7.4.0-5 image manipulation programs -- infrastructure rc libgraphicsmagick++3 1.3.12-1.1+b1 format-independent image processing - C++ shared library rc libgraphicsmagick3 1.3.12-1.1+b1 format-independent image processing - C shared library ii libmagickcore-dev 8:6.7.4.0-5 low-level image manipulation library - development files rc libmagickcore4 8:6.6.9.7-7 low-level image manipulation library ii libmagickcore5:i386 8:6.7.4.0-5 low-level image manipulation library ii libmagickcore5-extra:i386 8:6.7.4.0-5 low-level image manipulation library - extra codecs ii libmagickwand-dev 8:6.7.4.0-5 image manipulation library - development files rc libmagickwand4 8:6.6.9.7-7 image manipulation library ii libmagickwand5:i386 8:6.7.4.0-5 image manipulation library