RushCheckでFloatのカスタマイズをしてみた

miura17292007-12-16


poor man's rubyでRushCheckを使うため、Floatのカスタマイズをしてみました。バグが出やすいところを集中的にテストできるようにランダムの分布を制御できるようにしました。
統計とか詳しくないので、これでいいのか知らないですが、正規分布の組み合わせで分布を表すようにしました。

ソースコードです。

# = specialfloat.rb

require 'rushcheck/float'

class SpecialFloat<Float

  def self.normal_dist_list
    [
      #   頻度 期待値, 分散 
      [    100,   0.0, 3.0],
      [    100, 100.0, 3.0],
      [     50,  50.0,  10.0],
    ]
  end

  @@total_weight = normal_dist_list.inject(0) {|t, e| t += e[0]}
  def self.arbitrary
    RushCheck::Gen.new do |n, r|
      cw = @@total_weight
      rc = nil
      normal_dist_list.each do |e|
        a, r = lrand(r)
        if cw == 0 or (a * cw).to_i < e[0] then
          rc = dist_normal(e[1], e[2], n, r)
          break
        end
        cw -= e[0]
      end

      rc
    end
  end

  private

  extend RushCheck::HsRandom

  # 渡されたランダムジェネレータを使って(0..1)の一様乱数を得る
  def self.lrand(r)
    n, r = random(r, 0, 1)
    [n.abs, r]
  end

  # 期待値e, 分散vの正規分布に従う乱数を返す
  def self.dist_normal(e, v, n, r)
    sn = ((1..12).inject(0.0) {|t, i| 
            a, r = lrand(r)
            t += a
          }) - 6
    sn * v + e
  end

end

normal_dist_listメソッドで分布を制御します。normal_dist_listは正規分布のパラメータの配列です。
正規分布のパラメータは、次の3要素の配列です。

  • 頻度 この値が高いほど選ばれる可能性が高くなる。整数
  • 期待値 正規分布の期待値。Float
  • 分散 正規分布の分散。Float

例えば、0と100付近に特異点があるので、そのあたりをしっかりチェックしたい、でも他も一応見ておきたいという時、こんな感じで指定できます。

 def self.normal_dist_list
    [
      #   頻度 期待値, 分散 
      [    100,   0.0, 3.0],
      [    100, 100.0, 3.0],
      [     50,  50.0,  10.0],
    ]
  end

これの分散がどうなるか分かりやすくするため、gnuplotを使ってグラフ化できるようにしました。今日1枚が上のサンプルの分布図です。rgplot(http://rubyforge.org/projects/rgplot)を使っています。
分布を表示するリストです。実行すると、カレントディレクトリにfoo.pngが生成されますので注意してください。

require 'rubygems'
require 'rushcheck'
require 'gnuplot'

require 'specialfloat'

OutputFormat = "png"
OutputFile = "foo.png"

Gnuplot.open do |gp|
  Gnuplot::Plot.new( gp ) do |plot|

    plot.terminal OutputFormat
    plot.output  OutputFile
    plot.title  "Special Float Distination"

    data = []
    rc = RushCheck::Assertion.new(SpecialFloat)do |a|
      data.push a
      true
    end
    rc.check(RushCheck::Config.new(10000))
    ma = data.max.to_i + 10
    mi = data.min.to_i - 10

    x = []
    y = []
    (mi*10..ma*10).each { |n| 
      p n
      x << (n / 10.0)
      y << 0
    }

    data.each do |n|
      y[(n * 10).to_i - mi*10] += 1
    end
    
    plot.data << Gnuplot::DataSet.new( [x, y] ) do |ds|
      ds.with = "dot"
      ds.notitle
    end
  end
end