はじめに
Ruby の小数は環境によって実際の値ではなく限りなく近い近似値としてメモリに保持するケースがあります。例えば0.1という数値も環境 A では「0.1000000000」でも環境 B では「0.10000000001」と表現され、計算結果に誤差が出る場合があります。
これは PC が2進数表現を用いている関係でどうしようもない問題です。
大抵は誤差がほぼ出ないので気が付きにくいのですが、たまに表現しきれない数値で計算した場合に地味に原因追及に悩むことがあります。
対応
誤差のサンプル
irb(main):031:0> 100 * 1.1
=> 110.00000000000001
有効桁数を明示的に定義する
irb(main):033:0> (100 * 1.1).round(1) # 四捨五入[1]
=> 110.0
irb(main):034:0> (100 * 1.1).floor(1) # 切り捨て[2]
=> 110.0
(main):035:0> (100 * 1.1).ceil(1) # 切り上げ[3]
=> 110.1
今回は1.1 をかけるので有効桁は 1/10の位としています。計算で用いる桁数を考慮して妥当な方法で丸める方法です。
BigDecimal を使用する
irb(main):037:0> (BigDecimal("100") * BigDecimal("1.1"))
=> 0.11e3
irb(main):038:0> (BigDecimal("100") * BigDecimal("1.1")).to_f
=> 110.0
BigDecimal[4] では小数で発生していた誤差なく表現できるため、誤差は発生しません。
おわりに
有効桁数が明らかなケースでは誤差はほぼ無視できる値なので round/floor/ceil を使えばよいと思います。
有効桁数が不明な場合は 誤差がどれくらいでるかわからないので BigDecimal の方が良いでしょう。
参照
[1] round, https://docs.ruby-lang.org/ja/latest/class/Float.html#I_ROUND
[2] floor, https://docs.ruby-lang.org/ja/latest/class/Float.html#I_FLOOR
[3] ceil, https://docs.ruby-lang.org/ja/latest/class/Float.html#I_CEIL
[4] BigDecimal, https://docs.ruby-lang.org/ja/latest/library/bigdecimal.html
コメント