Help us understand the problem. What is going on with this article?

[Ruby]消費税計算にはBigDecimalを使いましょう

More than 5 years have passed since last update.

消費税計算でありがちな浮動小数点問題

ちょっと電卓で 1800 x 1.08 を計算してみてください。いくつになりましたか?

はい、 1944 ですね。

ではターミナルからirbを開いて、 1800 * 1.08 と打ち込んでください。

> 1800 * 1.08
=> 1944.0000000000002

あれ?1944にならない!?

はい、これはコンピュータを使った計算でよくありがちな、 浮動小数点問題 というやつです。
つまり、ざっくりいえばコンピュータは小数点を人間が行う計算と同じように扱えない、というわけです。

もし、「消費税計算は端数切り上げとする」という仕様だった場合、この浮動小数点問題はバグの原因となります。

> (1800 * 1.08).ceil
=> 1945

お金の計算は一円でもズレると致命的です。
どのように回避すれば良いでしょうか?

小数を含む金額の計算はBigDecimalを使いましょう

この問題を解決する方法には選択肢がいくつかあると思いますが、ここではRuby標準のBigDecimalクラスを使った計算方法を紹介します。

先ほどの計算はこうすれば期待する結果が得られます。

> require 'bigdecimal'
=> true
> (BigDecimal("1800") * BigDecimal("1.08")).ceil
=> 1944

ちなみに数値リテラル(Float型)をそのままBigDecimalに渡すと、ArgumentError: can't omit precision for a Float.というエラーが出ます。

> (BigDecimal(1800) * BigDecimal(1.08)).ceil
ArgumentError: can't omit precision for a Float.

これは、

  • もし小数がそのまま渡せてしまう場合、1.08という小数リテラルが評価された段階で、Floatクラスのインスタンスができる
  • しかしFloatクラスなので、その時点で 1.08 との誤差が生じてしまい、意図した結果にならない

ためです。なので、上で書いたように文字列として渡してください。

RSpecで検証

ついでにRSpecで実行結果を確認しておきましょう。

require 'bigdecimal'

describe 'tax calculation' do
  describe 'with Float' do
    it 'cannot get valid result' do
      expect((1800 * 1.08).ceil).to_not eq 1944
    end
  end
  describe 'with BigDecimal' do
    it 'gets valid result' do
      expect((BigDecimal("1800") * BigDecimal("1.08")).ceil).to eq 1944
    end
  end
end

余談: 消費税が5%のときはバグにならない

ちなみに上の計算は消費税が5%だと運良く(?)端数が出ないので、不具合になりません。

> 1800 * 1.05
=> 1890.0

なので、もしかすると「今まで問題なかったのに、なぜか8%になったら不具合が出始めた!」なんていうケースもあるんじゃないでしょうか?

まとめ

「プログラマだったら浮動小数点問題なんて常識だよ!」という方も多いと思いますが、常識だとわかっていても意外とうっかり間違えたりしてしまうものです。

はい、何を隠そう、僕自身がうっかりこの不具合を出してしまいました・・・。

僕のうっかりミスを他山の石として、みなさんも消費税計算や小数が絡む計算をする場合には浮動小数点の扱いを間違えないように注意してくださいね~。

参考

jnchito
SIer、社内SEを経て、ソニックガーデンに合流したプログラマ。 「プロを目指す人のためのRuby入門」の著者。 http://gihyo.jp/book/2017/978-4-7741-9397-7 および「Everyday Rails - RSpecによるRailsテスト入門」の翻訳者。 https://leanpub.com/everydayrailsrspec-jp
https://blog.jnito.com/
sonicgarden
「お客様に無駄遣いをさせない受託開発」と「習慣を変えるソフトウェアのサービス」に取り組んでいるソフトウェア企業
http://www.sonicgarden.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした