k-tokitoh

2019-08-17

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

.

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

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

10 進数の 1 も 1000000 も-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 をベースにした言葉をつかう。

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