【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ですね。

Rubyのバージョンについて
ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-darwin14] でのお話です。

Rational クラスを利用する

Rationalを利用すると 消費税率 1.08108/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のほうに自動的に切り替えてくれるので安全ですね。

(安全じゃないパターンもあるのでしょうか?)