55636


小数点を含む割り算の計算結果が思い通りになりません。

例えば、 `670 ÷ 0.67` としたとき `1000` になってほしいのですが、 `999.9999999999999` となってしまいます。

[code]

    $ ruby -v
    ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin18]
    
    $ irb
    irb(main):001:0> 670 / 0.67
    => 999.9999999999999
    
[/code]

Node.jsでも同じでした。

[code]

    $ node
    Welcome to Node.js v12.1.0.
    Type ".help" for more information.
    > 670 / 0.67
    999.9999999999999
    
[/code]

割り切れる数値だけ扱うプログラムなのですがうまく作れずに困っています。  
こんな簡単な計算で躓くと思っていなかったので、小数を扱うときに気をつけることがいろいろありそうで不安です。

なぜこのような計算ができないのか、またどのように処理を書くのが正しいのか教えていただけると幸いです。

環境はmacOSです。


CC BY-SA 4.0
2019-06-08T09:04:54.047
55632
2019-06-08T22:33:01.330
32878
question
ruby
Rubyで計算がずれてしまう理由と対策を教えてください。


Ruby(やJavaScript)に限らず、小数を表すデフォルトのデータ型として2進浮動小数点数を使用している言語(C#, Java,
Swift等々、よく見る主要な言語の殆どと言っていい)に共通して見られる現象です。

簡単に言うと **2進浮動小数点数では`0.67`のような値を正確に表現することができないから** と言う事になります。

より詳しい解説については、ここ日本語版スタックオーバーフローでも「計算
誤差」辺りで検索すると、いろいろヒットしますので、興味があればご自身で調べて見てください。例えば、私がJavaScriptでの同様の問題について書いたものは[こちら](https://ja.stackoverflow.com/a/49380/13972)。

**またどのように処理を書くのが正しいのか** については、目的によります。

ざっくり挙げていくと

  * 内部的にも10進演算を行うライブラリを使用する

Rubyなら`BigDecimal`

  * 四捨五入をうまくやって誤魔化す

Excelは実はこれ

  * コンピュータでの演算はこんなものだと開き直る

科学技術計算では演算誤差が入るのは当たり前

macOS上のirbで`BigDecimal`を使った例はこちら。

[code]

    irb(main):001:0> require 'bigdecimal'
    => true
    irb(main):002:0> a = BigDecimal("670")
    => #<BigDecimal:7fad04015f78,'0.67E3',9(18)>
    irb(main):003:0> b = BigDecimal("0.67")
    => #<BigDecimal:7fad0400dd00,'0.67E0',9(18)>
    irb(main):004:0> print (a/b).to_s('F')
    1000.0=> nil
    
[/code]


CC BY-SA 4.0
2019-06-08T12:29:08.633
55636
2019-06-08T12:29:08.633
13972
55632
answer


なぜできないのか、を順を追って細かく説明します。

  1. `0.67`はFloatのリテラルである。
  2. Rubyの[`Float`](https://docs.ruby-lang.org/ja/latest/class/Float.html)はCの[`double`](https://en.cppreference.com/w/c/language/arithmetic_types#Real_floating_types)によって実装されており、メモリ上の表現は **環境依存** である。
  3. macOSを含めたほとんど全ての環境において、Cの`double`は[倍精度浮動小数点数(IEEE 754 binary64)](https://ja.wikipedia.org/wiki/%E5%80%8D%E7%B2%BE%E5%BA%A6%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0)を表現方法に使用している。(Cの仕様では **倍精度浮動小数点数でなければならないという制約はない** が、倍精度浮動小数点数ではない環境を私は知らない。)
  4. 倍精度浮動小数点数は基数が2である(2進数として管理している)ため、十進数表記の"0.67"になる数値を正確に表現することはできない。(irb等で`printf("%.20f\n", 0.67)`と打ってみればわかる。)
  5. よって、Rubyでの`0.67`というリテラルは十進数表記の"0.67"そのもの数値では **なく** 、"0.67"に極めて近いが **異なる** 数値になる。ただし、その誤差は倍精度浮動小数点数がもつ精度の範囲内である。
  6. 対して、`670`はIntegerであり、"670"そのものであるが、`/`の計算にあたり、Floatに変換される([Numeric#coerce](https://docs.ruby-lang.org/ja/latest/method/Numeric/i/coerce.html)という仕組みになっているが、Cレベルの実装では個別に最適化している場合がある)。しかし、Floatでも`670`は倍精度浮動小数点数が表現可能な整数のみの範囲内であるため、"670"そのものである。
  7. "670"を"0.67"に極めて近い数値で割ることになるため、"1000"に近い数値なるが、倍精度浮動小数点数が持つ精度の範囲内での誤差により、"1000"そのものになるとは限らない。

科学の世界では測量誤差があるため、倍精度浮動小数点数の精度が測量誤差の範囲内の場合は問題になりません(問題になる場合は4倍精度浮動小数点数を使うようです)。しかし、会計処理など極めて厳密な十進数での計算が必要な時は問題になります。BigDecimalを使う方法もありますが、Rubyでは[Rational](https://docs.ruby-
lang.org/ja/latest/class/Rational.html)を使う方法もあります。

[code]

    ~> pry
    [1] pry(main)> a = 670
    => 670
    [2] pry(main)> b = 67r / 100
    => (67/100)
    [3] pry(main)> a / b
    => (1000/1)
    
[/code]


CC BY-SA 4.0
2019-06-08T22:33:01.330
55638
2019-06-08T22:33:01.330
7347
55632
answer