Diary

Diary

日々学んだことをアウトプットする場として初めてみました

awk で小数点以下を含む計算するときの注意

awk で小数点以下を含む計算するときの注意

少数を含む計算の注意点

小数点以下が有限の桁で記述できないような少数は、ある程度の桁数で近似を行うしかないため、以下のような(一見)不思議な現象が起きます。

$ awk 'BEGIN {print int(70.21 * 100)}'
7020

# なお、整数部分の桁数に依存するようです
$ awk 'BEGIN {print int(30.21 * 100)}'
3021
$ awk 'BEGIN {print int(1000.21 * 100)}'
100021

これは awk が特別なのではなく、python でも同様のことが起こります。

$ python
>>> int(70.21*100)
7020

PC が数値を2進数でどのように扱うかに依存する問題なので、当たり前と言えば当たり前な気がします。

少数部分を求めてみる

今回問題の小数点以下の部分、0.21 の2進数表記を awk で求めてみました。

以下の例では 44 桁まで計算しています。

$ awk 'BEGIN{x=0.21; for(i=1; i<=44; i++){if(x >= 1/2^i){x -= 1/2^i; printf 1}else{printf 0}}; print ""}'
00110101110000101000111101011100001010001111

2進数表記の際に、有限桁数で記述できるかどうか

ここでは話を変えて、ある10進数の数が与えられた時に、2進数表記で割り切れるのかと言う問題について考えてみたいと思います。

ここでは2進数での 0.1 を 2-1=0.5 だと計算しています

ある数Nが有限の m 桁で表せたとき

f:id:kokoichi206:20210811220943p:plain

とかけます。

そのことから、約分した時に、分母が 2 以外の約数を持つときは有限の桁数で記述できないことがわかります。

例えば「0.0019073486328125 = 125/2**16」であるので、この数は2進数で現せます。

$ awk 'BEGIN{x=0.0019073486328125; for(i=1; i<=44; i++){if(x >= 1/2^i){x -= 1/2^i; printf 1}else{printf 0}}; print ""}'
00000000011111010000000000000000000000000000

おわりに

awk での計算の注意とか言っておきながら、結局2進数表記のことを語ってしまいました。

内部まで理解していきたいです。