【Ruby】expect(0.1 + 0.2 == 0.3).to be_falsey
2015-11-18
ruby
$ irb
irb(main):001:0> 0.1 + 0.2 == 0.3
=> false
浮動小数点絡みの問題ですね。
有名な例ですが、消費税の計算(8%)、端数切り上げの場合を見てみます。
(450 * 1.08).ceil
=> 487
電卓等で計算すると 486ですね。
Rational クラスを利用する
Rationalを利用すると 消費税率 1.08
を 108/100r
と表現できます。
(450*108/100r).ceil
=> 486
0.1 + 0.2 == 0.3
を Rational で計算してみます。
(1/10r + 2/10r) == 0.3
=> true
true
になりました!
(右辺を 3/10r
とするべきなのかはよく調べてないです。)
to_r
メソッドもあります。
"1/2".to_r
# => (1/2)
"0.5".to_r
# => (1/2)
0.5.to_r
# => (1/2)
ただし、以下の場合はこんな変なことになるので注意
0.2.to_r
=> (3602879701896397/18014398509481984)
"0.2".to_r
=> (1/5)
なんかバグっぽい挙動ですね。
BigDecimal を使う
require 'bigdecimal'
(BigDecimal.new("450") * BigDecimal.new("1.08")).ceil
=> 486
BigDecimal.new("0.1") + BigDecimal.new("0.2") == 0.3
=> true
なんか面倒ですね。
Railsの場合
例えば、日付によって有効な税率を切り替えるような実装をしている場合
# migration file
class CreateTaxes < ActiveRecord::Migration
def change
create_table :taxes do |t|
t.decimal :rate, precision: 3, scale: 2
t.timestamps null: false
end
end
end
$ bin/rails c
Loading development environment (Rails 4.2.5)
irb(main):001:0> Tax.create!(rate: 1.08)
irb(main):002:0> tax = Tax.first
=> #<Tax id: 1, rate: #<BigDecimal:7fd7970fac28,'0.108E1',18(27)>, created_at: "2015-11-17 16:03:12", updated_at: "2015-11-17 16:03:12">
irb(main):003:0> (450 * tax.rate).ceil
=> 486
BigDecimalのほうに自動的に切り替えてくれるので安全ですね。
(安全じゃないパターンもあるのでしょうか?)