問題
Rubyで0.1を足していくと、計算結果がずれるという問題が起きた。例えば、以下のようなコードを書くと、期待しているような計算結果が得られない。
> tmp = 0.0 => 0.0 > 1..10.times { p tmp += 0.1 } 0.1 0.2 0.30000000000000004 0.4 0.5 0.6 0.7 0.7999999999999999 0.8999999999999999 0.9999999999999999 => 1..10
経緯
twitterでぼやいたら、リプライで親切に教えていただけた。
@akiroom 2.1で導入されたrationalリテラルで正確に計算できそう
— Tomotaka Ito (@tomotaka_ito) January 10, 2014
https://twitter.com/kalab1998/status/421575612789035008
@akiroom http://t.co/IDuY7zlmqp
— yoshitomi TOKITOMO (@ymrl) January 10, 2014
@akiroom 余計なお世話だと存じ上げますが、小数点以下の計算は2進数で計算すると10進数では循環せずとも、2進数では循環小数になってしまう事があるためそのような結果が出たりします。他の方がおっしゃっているようにrationalリテラルを利用するとよろしいかもしれません。
— まんじゅ(´ん`)@放浪者 (@manzyun) January 10, 2014
また、調べると浮動小数点数どうしが同値であるかを比較には「計算機イプシロン」を使って誤差を吸収しなければならないらしい。
詳しくは浮動小数点数の同値比較には計算機イプシロンを使うこと – Tociyuki::Diaryを参照。
結論
- IEEE 754の規格上の仕様
- Ruby2.1以上ならrationalリテラルを使って、
tmp = 0.0r
というように書く - Ruby2.1未満ならBigDecimalを使う
- 浮動小数点数を比較する時は計算機イプシロン Float::EPSILON を利用する
ちなみに、RT先でコンピュータサイエンスの基本を理解していないという指摘もあった。耳の痛い話だった。