お題
体重と身長だけから算出されるおおざっぱな肥満度の指数として BMI(Body Mass Index)というものがありますね。
体重を $w$ kg,身長を $h$ m とすると,
\textrm{BMI} = \frac{ w }{ h^2 }
と表されます。18.5 以上 25 未満が標準だそうです。
では,体重を kg で,身長を cm で与えると BMI 値を返すメソッドを定義してみましょう。上の式では身長の単位は m ですが,cm のほうが扱いやすいので,cm にしましょう。すると 10000 を掛けることになりますね。
コード
ときどき,こういうコード(に類するもの)を目にします。
def bmi(weight, height)
10_000 * weight / height ** 2
end
問題点
このメソッドは,正確な値を返さないことがあります。
Ruby の除算演算子 /
は,非除数(割られる数),除数(割る数)がいずれも Integer オブジェクトのとき,普通の商ではなく整商(整数の商)を返す仕様なのです。
つまり,
p 5 / 4.0 # => 1.25
p 5 / 4 # => 1
となります。
したがって,体重 55 kg,身長 172 cm の人は BMI 値が本来は
18.591130340724717
と標準の範囲に入るはずですが,引数を整数で与えると
p bmi(55, 172) # => 18
となって,「低体重」とされてしまいます。
返り値が正しい値と少ししか違わないので,気づきにくいバグと言えます。
改善
/
のような,数値のクラスによって意味がガラッと変わる演算子は要注意です。
今の場合,Numeric#fdiv を用いて
def bmi(weight, height)
10_000 * weight.fdiv(height ** 2)
end
とするとか,Integer リテラルの 10_000
を Float リテラルの 10_000.0
に代えて
def bmi(weight, height)
10_000.0 * weight / height ** 2
end
とすれば直ります。