なぜBigDecimalでは高い精度で10進数を扱えるのか

  • 「Floatだと丸め誤差が発生するから10進数を扱う精度が低い」
  • BigDecimalだと丸め誤差が発生しないから10進数を扱う精度が高い」

という話に触れたのだが、その理由がぱっと理解できなかったので、ちょっと整理して考えてみた。*1

(前提)10進数の整数は2進数で正確に表現できる

10進数の1も10000000も-12345も、2進数で正確に表現できる。*2

10進数の小数はFloatでは正確に表現できない

10進数の0.1(0d0.1と表記する)を数直線上で表すと以下。

0  0d0.1                                  1
|----|------------------------------------|

一方、Floatがつかう目盛りは以下(2進数の0.1を0b0.1と表記する)。

0         0b0.01                            1
|-----|-----|----------|--------------------|
   0b0.001           0b0.1

2進数の世界からみると0d0.1というのはめちゃくちゃ曖昧な値なので、なんとかして2進数の組み合わせで表現しようとする(無限小数)。

しかしバイト数には制約があるので一定以下の桁の情報は捨てられる。

よって2進数では0d0.1を不正確にしか表現できない。

10進数の小数はBigDecimalでは正確に表現できる

BigDecimalでは、10進数の小数を直接2進数の小数で表現しようとはせず、以下の流れを経る。

0d0.1を表現したい
↓
それってつまり1*(10**-1)
↓
基数部の1と指数部の-1という情報によって0d0.1を表現する

一般化すると以下。

10進数の小数を表現したい
↓
それってつまりa*(10**b)  (10進数の小数は必ず整数a,bによりこの形で表現できる)
↓
基数部のa(整数)と指数部のb(整数)という情報によって10進数の小数を表現する

a*(10**b)に言い換える時点で、何も情報は失われていない。

そして基数部と指数部の整数は、コンピュータ上で2進数で処理されたとしても、何も情報は失われない。

(なぜなら上述したとおり「10進数の整数は2進数でも正確に表現できる」から。)

よってBigDecimalでは10進数の小数を正確に表現することができる。

*1:Rubyに限ったトピックではないが、ここではRubyをベースにした言葉をつかう。

*2:データの固定長による制約とかはここでは無視する。