2008年8月27日水曜日

Python で正確な小数の計算 (1) - Decimal モジュールを使う

1. 思っていた答えと違った少数の計算

実際に行った計算の例を挙げる。

29 - (54.2 - 52.0) / 0.1

暗算すれば、答えは 7 ということはわかる。

ある実装において、上記の計算に対して、

  1. int() 関数を適用して、
  2. その値を元に、配列の要素を取得する

という操作を行った。その結果、想定していた挙動と違い、ハマった。 (+_+)

print range(0,10)[int(29 - (54.2 - 52.0) / 0.1)]

7 という答えが返ってくるかと思いきや 6 。

 

2. いろいろな丸め関数

int() 関数について調べると、2.1 組み込み関数 には、

int([x[, radix]])

文字列または数値を通常の整数に変換します。(…)浮動小数点数から整数へ変換では (ゼロ方向に) 値を丸めます。引数が通常整数の範囲を超えている場合、長整数が代わりに返されます。引数が与えられなかった場合、0 を返します。

(太字は引用者による)

「ゼロ方向に値を丸める」というところに注目。試してみる。

print int(0.1), int(0.5), int(0.5), int(0.9)

結果は、切り捨てのような結果になる。

0 0 0 0

 

round, ceil, floor 関数

似たような関数 (round, ceil, floor) について試しておく。

print round(0.1), round(0.4), round(0.5), round(0.9)
import math
print math.ceil(0.1), math.ceil(0.4), math.ceil(0.5), math.ceil(0.9)
print math.floor(0.1), math.floor(0.4), math.floor(0.5), math.floor(0.9)

結果は、

0.0 0.0 1.0 1.0
1.0 1.0 1.0 1.0
0.0 0.0 0.0 0.0

それぞれ、「四捨五入、切り上げ、切り捨て」の計算が行われる。

 

3. 浮動小数点と固定小数点の違い

    print type(54.2)

を実行すると、

<type 'float'>

と値が返される。つまり、54.2 は浮動小数点。

浮動小数点数 – Wikipedia とは、、

固定小数点数と比較するとさまざまな誤差が発生しやすいが、大きな値や、逆に小さな値を表現するのに向いている。そのため、誤差の概念がはっきりしている分野や極端な数を扱う分野(科学計算など)で多く用いられている。

これに対して、固定小数点数 – Wikipedia は、

浮動小数点数に比べて表現できる値の範囲ははるかに狭いが、情報落ちが起こらないことや高速に演算できることが利点に挙げられる。

大きな値を扱えるけれど誤差が生じてしまう「浮動小数点」と、扱える値は浮動小数点に比べて小さいけれど正確で高速な「固定小数点」という対立する二つの小数表現がある。

そういえば、浮動小数点の表現方法なんて忘れてた… ^^;

浮動小数点の誤差の種類に関しては、エラー(誤差) を参照。

 

4. 正確な少数の計算が行える Decimal クラス

Python には、浮動小数点を表す float 以外に、Decimal クラスが存在する。

11. 標準ライブラリミニツアー - その 2 の「11.8 10 進浮動小数演算」によると、

decimal では、 10 進浮動小数の算術演算をサポートする Decimal データ型を提供しています。組み込みの 2 進浮動小数の実装である float に比べて、この新たなクラスがとりわけ便利なのは、厳密な 10 進表記計算精度の制御法的または規制上の理由に基づく値丸めの制御有効桁数の追跡が必要になる金融計算などのアプリケーションや、ユーザが手計算の結果と同じ演算結果を期待するようなアプリケーションの場合です。

(装飾は引用者による)

float , int() 関数を適用した結果と、Decimal 型で行う計算を比較してみる。

a = 29 - (54.2 - 52.0) / 0.1
b = int(29 - (54.2 - 52.0) / 0.1)
from decimal import Decimal
c = 29 - (Decimal('54.2') - Decimal('52.0')) / Decimal('0.1')

print a, b, c

結果は、それぞれ

7.0 6 7

上記の変数をリストに入れ、print を適用すると、より詳しく内容を見ることができる。

print [a, b, c]
[6.9999999999999716, 6, Decimal("7")]

これで int() 関数を適用した結果が 7 ではなく、 6 となってしまった理由がわかった。 float 型の計算の結果、誤差が生じていたということ。

 

0.1 を浮動小数点では正確に表現できない

0.1 を2 進浮動小数では、スパッと表現できない。

print [0.1, 0.5]
[0.10000000000000001, 0.5]

追記(2008.9.8) : B. 浮動小数点演算、その問題と制限 によると、

浮動小数点数は、計算機ハードウェアの中では、基数を 2 とする (2進法の) 分数として表現されています。例えば、小数

0.125

は、 1/10 + 2/100 + 5/1000 という値を持ちますが、これと同様に、 2 進法の分数

0.001

は 0/2 + 0/4 + 1/8 という値になります。これら二つの分数は同じ値を持っていますが、ただ一つ、最初の分数は基数 10 で記述されており、二番目の分数は基数 2 で記述されていることが違います。

残念なことに、ほとんどの小数は 2 進法の分数として正確に表わすことができません。その結果、一般に、入力した 10 進の浮動小数点数は、 2 進法の浮動小数点数で近似された後、実際にマシンに記憶されます。

(via str()関数とrepr()関数 - バリケンのPython日記 - pythonグループ)

 

dicimal の意味

Yahoo!辞書 – decimal によると、

[形]

1 小数の

a decimal fraction
小数

2 十進法の

decimal classification
十進法図書分類法

━━[名]小数.

[中ラテン語. ラテン語decima(10分の1)+-AL. △DIME

なるほど、「10 分の1」 という意味から来ているのか。

 

5. 関連記事