読者です 読者をやめる 読者になる 読者になる

ネットの海の片隅で

技術ネタの放流、あるいは不法投棄。

Rubyでゼロ除算になるかもしれないときの書き方

やりたいこと

分母に0が来る可能性がある除算をRubyで書くときに、すっきり書きたい。

考えるケース

店(Shop)がそれぞれ複数の在庫(Stock)を持っているシンプルなケースを考える。イメージとしては下のような感じ。

class Shop
  has_many :stocks
end

class Stock
  attr_accessor :price
end

このケースで店舗にある在庫の平均価格を求めるメソッドShop#average_priceを定義する。ただし、在庫が1つもないときは0を返す。*1

1. 素直に書いてみる

class Shop
  def average_price
    stocks.size > 0 ? stocks.map(&:price).reduce(0, :+) / stocks.size : 0
  end
end

世の中の多くのコードはこのパターンで書かれている気がする。よく使われている分、わかりやすいと言えばわかりやすいが、stocks.sizeが2箇所に現れているのが気になる。

2. 例外を使うパターン その1

class Shop
  def average_price
    stocks.map(&:price).reduce(0, :+) / stocks.size
  rescue ZeroDivisionError
    0
  end
end

個人的に好きな書き方。欲しい物をシンプルに書いて、ゼロ除算の例外を補足する。

メソッド中などのようにreturnする場合は良いが、返り値をそのまま変数に代入したい場合にはあまり実用的でないのがデメリット。

foo = begin
  100 / 0
rescue ZeroDivisionError
  0
end

3. 例外を使うパターン その2

class Shop
  def average_price
    stocks.map(&:price).reduce(0, :+) / stocks.size rescue 0
  end
end

「例外を使うパターン その1」で扱いにくかった変数への代入に使えるパターン。

foo = 100 / 0 rescue 0

デメリットとしてはZeroDivisionError以外の例外も握りつぶしてしまうこと。

まとめ

どれも一長一短という感じです。

その場でしか使わないような数値に対しては最も一般的な1のパターンも使っていきますが、理想としては丁寧にメソッドを切り出した中でZeroDivisionErrorを補足するのが良いと思っています。

参考:きわめて強いゼロ(very strongly zero)*2

コンピュータの数学』のp.26にきわめて強いゼロ(very strongly zero)というものが載っている。

きわめて強いゼロに対する演算は

  • 定義されていない値の乗算を行なっても
  • 0による除算を行なっても

結果が0になる。

*1:場合によっては0ではなくnilを返すかもしれないが、書き方としては同じ。

*2:very strong zeroじゃないかと思うが、原文ママ