decimal – 固定小数点数と浮動小数点数

目的:固定小数点数と浮動小数点数を扱う10進数の計算
利用できるバージョン:2.4 以上

decimal モジュールは、コンピュータのハードウェアが実装した IEEE の浮動小数点数より、人間にとって分かりやすいモデルの固定小数点数と浮動小数点数を実装します。Decimal インスタンスは、任意の数を正確に表したり、端数の切り上げや切り下げ、有効桁数の制限を設けます。

Decimal

Decimal の値は Decimal クラスのインスタンスとして表されます。コンストラクタの引数は整数か文字列を受け取ります。 Decimal で浮動小数点数を表すには、その引数を文字列に変換しなければなりません。そして Decimal は、ハードウェアの浮動小数点数では正確に表せない桁の値を呼び出し側で明示的に指定できます。

import decimal

fmt = '{0:<20} {1:<20}'
print fmt.format('Input', 'Output')
print fmt.format('-' * 20, '-' * 20)

# 整数
print fmt.format(5, decimal.Decimal(5))

# 文字列
print fmt.format('3.14', decimal.Decimal('3.14'))

# 浮動小数
print fmt.format(repr(0.1), decimal.Decimal(str(0.1)))

0.1 という値の浮動小数点数は、厳密な値の表現ではないので、その浮動小数表現は Decimal の値とは違うことに注意してください。

$ python decimal_create.py
Input                Output
-------------------- --------------------
5                    5
3.14                 3.14
0.1                  0.1

あまり使い勝手が良くないですが、Decimal は符号のフラグ (0 は正, 1 は負)、それぞれの桁の値を表すタプル、整数の指数の3つの値をもつタプルからも作成されます。

import decimal

# タプル
t = (1, (1, 1), -2)
print 'Input  :', t
print 'Decimal:', decimal.Decimal(t)
$ python decimal_tuple.py
Input  : (1, (1, 1), -2)
Decimal: -0.11

計算

Decimal は、単純な算術演算子をオーバーロードするので、ある値が与えられたときに組み込みの数値型とほぼ同じようにその値を計算します。

import decimal

a = decimal.Decimal('5.1')
b = decimal.Decimal('3.14')
c = 4
d = 3.14

print 'a     =', a
print 'b     =', b
print 'c     =', c
print 'd     =', d
print

print 'a + b =', a + b
print 'a - b =', a - b
print 'a * b =', a * b
print 'a / b =', a / b
print

print 'a + c =', a + c
print 'a - c =', a - c
print 'a * c =', a * c
print 'a / c =', a / c
print

print 'a + d =',
try:
    print a + d
except TypeError, e:
    print e

また Decimal の演算子は整数を受け取って計算できますが、浮動小数の場合は Decimal インスタンスに変換する必要があります。

$ python decimal_operators.py
a     = 5.1
b     = 3.14
c     = 4
d     = 3.14

a + b = 8.24
a - b = 1.96
a * b = 16.014
a / b = 1.624203821656050955414012739

a + c = 9.1
a - c = 1.1
a * c = 20.4
a / c = 1.275

a + d = unsupported operand type(s) for +: 'Decimal' and 'float'

対数

基本的な計算に加えて、自然対数や10を底とする常用対数を調べるメソッドも提供します。

import decimal

d = decimal.Decimal(100)
print 'd     :', d
print 'log10 :', d.log10()
print 'ln    :', d.ln()
$ python decimal_log.py
d     : 100
log10 : 2
ln    : 4.605170185988091368035982909

特殊な値

期待される数値表現に加えて、 Decimal は、正負の無限大、”数値ではない”、ゼロを含む特殊な値を表現できます。

import decimal

for value in [ 'Infinity', 'NaN', '0' ]:
    print decimal.Decimal(value), decimal.Decimal('-' + value)
print

# 無限大
print 'Infinity + 1:', (decimal.Decimal('Infinity') + 1)
print '-Infinity + 1:', (decimal.Decimal('-Infinity') + 1)

# NaN との比較結果を表示する
print decimal.Decimal('NaN') == decimal.Decimal('Infinity')
print decimal.Decimal('NaN') != decimal.Decimal(1)

無限大に値を加算すると、別の無限大の値を返します。NaN と等式で比較すると必ず False を返し、不等式で比較すると必ず True を返します。NaN に対してソートの比較を行うと、未定義のエラーが発生します。

$ python decimal_special.py
Infinity -Infinity
NaN -NaN
0 -0

Infinity + 1: Infinity
-Infinity + 1: -Infinity
False
True

コンテキスト

これまでの全てのサンプルは、 decimal モジュールのデフォルトの処理を使用してきました。保持される精度、丸め、エラー処理といった設定は上書きできます。こういった全ての設定は コンテキスト 経由で行われます。コンテキストは、ローカルの小さなコード内か、スレッド内の全ての Decimal インスタンスに適用されます。

カレントコンテキスト

カレントのグローバルコンテキストを取り出すには getcontext() を使用してください。

import decimal

print decimal.getcontext()
$ python decimal_getcontext.py
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, capitals=1, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])

精度

コンテキストの prec 属性は、計算の結果として生成される新たな値が保持する精度を制御します。リテラル値は前節で説明したように保持されます。

import decimal

d = decimal.Decimal('0.123456')

for i in range(4):
    decimal.getcontext().prec = i
    print i, ':', d, d * 1
$ python decimal_precision.py
0 : 0.123456 0
1 : 0.123456 0.1
2 : 0.123456 0.12
3 : 0.123456 0.123

丸め

必要な精度の範囲内に値が収まるように丸めのオプションがあります。

ROUND_CEILING
常に正の無限大へ切り上げて丸める
ROUND_DOWN
常にゼロ方向に丸める
ROUND_FLOOR
常に負の無限大へ丸める
ROUND_HALF_DOWN
最後の桁を五捨六入して丸める
ROUND_HALF_EVEN
ROUND_HALF_DOWN と同じように処理されますが、最後の桁の値が 5 の場合、精度の桁を調べて、偶数なら切り捨て、奇数なら切り上げる
ROUND_HALF_UP
ROUND_HALF_DOWN と同じように処理されますが、最後の桁を四捨五入する
ROUND_UP
ゼロから遠い値に丸める
ROUND_05UP
最後の桁が 05 ならゼロから遠い値へ、それ以外はゼロに丸める
import decimal

context = decimal.getcontext()

ROUNDING_MODES = [ 
    'ROUND_CEILING', 
    'ROUND_DOWN',
    'ROUND_FLOOR', 
    'ROUND_HALF_DOWN', 
    'ROUND_HALF_EVEN',
    'ROUND_HALF_UP',
    'ROUND_UP',
    'ROUND_05UP',
    ]

header_fmt = '{0:20} {1:^10} {2:^10} {3:^10}'

print 'POSITIVES:'
print

print header_fmt.format(' ', '1/8 (1)', '1/8 (2)', '1/8 (3)')
print header_fmt.format(' ', '-' * 10, '-' * 10, '-' * 10)
for rounding_mode in ROUNDING_MODES:
    print '{0:20}'.format(rounding_mode),
    for precision in [ 1, 2, 3 ]:
        context.prec = precision
        context.rounding = getattr(decimal, rounding_mode)
        value = decimal.Decimal(1) / decimal.Decimal(8)
        print '{0:<10}'.format(value),
    print

print
print 'NEGATIVES:'

print header_fmt.format(' ', '-1/8 (1)', '-1/8 (2)', '-1/8 (3)')
print header_fmt.format(' ', '-' * 10, '-' * 10, '-' * 10)
for rounding_mode in ROUNDING_MODES:
    print '{0:20}'.format(rounding_mode),
    for precision in [ 1, 2, 3 ]:
        context.prec = precision
        context.rounding = getattr(decimal, rounding_mode)
        value = decimal.Decimal(-1) / decimal.Decimal(8)
        print '{0:<10}'.format(value),
    print
$ python decimal_rounding.py
POSITIVES:

                      1/8 (1)    1/8 (2)    1/8 (3)
                     ---------- ---------- ----------
ROUND_CEILING        0.2        0.13       0.125
ROUND_DOWN           0.1        0.12       0.125
ROUND_FLOOR          0.1        0.12       0.125
ROUND_HALF_DOWN      0.1        0.12       0.125
ROUND_HALF_EVEN      0.1        0.12       0.125
ROUND_HALF_UP        0.1        0.13       0.125
ROUND_UP             0.2        0.13       0.125
ROUND_05UP           0.1        0.12       0.125

NEGATIVES:
                      -1/8 (1)   -1/8 (2)   -1/8 (3)
                     ---------- ---------- ----------
ROUND_CEILING        -0.1       -0.12      -0.125
ROUND_DOWN           -0.1       -0.12      -0.125
ROUND_FLOOR          -0.2       -0.13      -0.125
ROUND_HALF_DOWN      -0.1       -0.12      -0.125
ROUND_HALF_EVEN      -0.1       -0.12      -0.125
ROUND_HALF_UP        -0.1       -0.13      -0.125
ROUND_UP             -0.2       -0.13      -0.125
ROUND_05UP           -0.1       -0.12      -0.125

ローカルコンテキスト

Python 2.5 以上を使用することで、さらに with 文とコンテキストマネージャでコードのサブセットに対してコンテキストを適用することもできます。

import decimal

with decimal.localcontext() as c:
    c.prec = 2
    print 'Local precision:', c.prec
    print '3.14 / 3 =', (decimal.Decimal('3.14') / 3)

print
print 'Default precision:', decimal.getcontext().prec
print '3.14 / 3 =', (decimal.Decimal('3.14') / 3)
$ python decimal_context_manager.py
Local precision: 2
3.14 / 3 = 1.0

Default precision: 28
3.14 / 3 = 1.046666666666666666666666667

インスタンスごとのコンテキスト

コンテキストは、精度の適用して入力の型から引数を丸めるように変換しながら、Decimal インスタンス作成するのに使用できます。これによりアプリケーションは、ユーザデータの精度から定数の値の精度を分離して選択できます。

import decimal

# コンテキストを制限された精度で設定する
c = decimal.getcontext().copy()
c.prec = 3

# 定数を作成
pi = c.create_decimal('3.1415')

# 定数の値は丸められる
print 'PI:', pi

# 定数を使用した結果はグローバルコンテキストを使用する
print 'RESULT:', decimal.Decimal('2.01') * pi
$ python decimal_instance_context.py
PI: 3.14
RESULT: 6.3114

スレッド

“グローバル” コンテキストは、実際にはスレッドローカルなので、それぞれのスレッドは潜在的に違う値を設定できます。

import decimal
import threading
from Queue import Queue

class Multiplier(threading.Thread):
    def __init__(self, a, b, prec, q):
        self.a = a
        self.b = b
        self.prec = prec
        self.q = q
        threading.Thread.__init__(self)
    def run(self):
        c = decimal.getcontext().copy()
        c.prec = self.prec
        decimal.setcontext(c)
        self.q.put( (self.prec, a * b) )
        return

a = decimal.Decimal('3.14')
b = decimal.Decimal('1.234')
q = Queue()
threads = [ Multiplier(a, b, i, q) for i in range(1, 6) ]
for t in threads:
    t.start()

for t in threads:
    t.join()

for i in range(5):
    prec, value = q.get()
    print prec, '\t', value
$ python decimal_thread_context.py
1       4
2       3.9
3       3.87
4       3.875
5       3.8748

See also

decimal
本モジュールの標準ライブラリドキュメント
Wikipedia: Floating Point
浮動小数点数表現と数値に関する記事
Bookmark and Share