4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Ruby のまずいコード】除算の落とし穴

Last updated at Posted at 2021-11-30

お題

体重と身長だけから算出されるおおざっぱな肥満度の指数として 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

とすれば直ります。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?