Decimal (十進浮動小数点数型)

Decimal (十進浮動小数点数型)

Decimalクラス (十進浮動小数点数型)

 組み込みの浮動小数点数型 (floatクラス) オブジェクトを使った数値計算には非常に厄介な一面があります。たとえば、0.1 + 0.1 + 0.1 - 0.3 というような簡単な演算でさえ正確な値を返してくれません(理由については浮動小数点数の記事を参照してください)。

x = 0.1 + 0.1 + 0.1 - 0.3

print(x)
5.551115123125783e-17

 誤差は極めて小さなものですが、開発するソフトウェアの種類によっては大きな障壁となる場合があります。こうした問題を回避するために、十進浮動小数点数型 (Decimalクラス) が用意されています。0.1 + 0.1 + 0.1 - 0.3 という計算をDecimalクラスのインスタンスを使って実行してみましょう。

# PYTHON_DECIMAL

# In[1]

# decimalモジュールをインポート
import decimal

# Decimalクラスのインスタンスを作成
x = decimal.Decimal('0.1')
y = decimal.Decimal('0.3')

# xのクラスを表示
print(type(x))

# 0.1 + 0.1 + 0.1 - 0.3を計算
print(x + x + x - y)
<class 'decimal.Decimal'>
0.0

 今度は正確に 0.0 という値を返しました。Decimalクラスは十進数を正確に表現できるように設計されています。decimal.Decimal(x) の引数 x に小数点を含む数字をクォーテーションまたはダブルクォーテーションで囲った文字列形式で渡すと、表記された通りの十進数の値 (Decimalクラスのインスタンス) を生成します。すなわち "1.3" は正確に十進数 1.3 を意味する Decimal型数値であり、小数点以下に(たとえば 1.3000001 のような)小さな誤差を伴う近似値ではありません。ただし、数字を囲わずに渡した場合は、メモリにある浮動小数点数をそのまま十進数の値として保存してしまうので注意してください。

# In[2]

# Decimalクラスのインスタンスを作成
x = decimal.Decimal(1.30)
y = decimal.Decimal(1)

print(x)
print(y)
1.3000000000000000444089209850062616169452667
1

 引数に整数型を指定すると、そのまま Decimal 型に変換されます(もともと整数値は進数変換で誤差を生じません)。

# In[3]

val = decimal.Decimal(1)

print(val)
1

 Decimal 型数値同士を演算させるとDecimal 型数値が返ります。Decimal 型数値には有効桁数の概念が反映されています。下のサンプルコードにあるように、1.40 + 1.20 は 2.60, 1.40 * 1.20 は 1.6800 というオブジェクトを生成します。除算については、割り切れない場合にはデフォルト設定で 28 桁の数値に丸められます(後述するように Context オブジェクトのデータ属性に値を代入して桁数設定を変えることもできます)。

# In[4]

x = decimal.Decimal('1.40')
y = decimal.Decimal('1.20')

# Decimalオブジェクト同士の演算
print("1.40 + 1.20 :", x + y)
print("1.40 - 1.20 :", x - y)
print("1.40 * 1.20 :", x * y)
print("1.40 / 1.20 :", x / y)
print("1.40 // 1.20 :", x // y)
print("1.40 % 1.20 :", x % y)
1.40 + 1.20 : 2.60
1.40 - 1.20 : 0.20
1.40 * 1.20 : 1.6800
1.40 / 1.20 : 1.166666666666666666666666667
1.40 // 1.20 : 1
1.40 % 1.20 : 0.20

 Decimal 型数値と int 型数値を演算させると、Decimal 型数値が返ります。

# In[5]

# Decimal型数値
x = decimal.Decimal('10.5')

# int型数値
y = 5

# Decimal + int
z = x + y

print(z)
print(type(z))
15.5
<class 'decimal.Decimal'>

 Decimal 型数値と float 型数値を演算させことはできません (TypeError が発生します)。
 

Contextオブジェクト

 getcontext()関数 を使うと、現在の Decimal 型の演算環境設定 (context) を表示させることができます。

# PYTHON_DECIMAL_CONTEXT

# In[1]

import decimal

# Contextオブジェクトを作成
x = decimal.getcontext()

print(type(x))
print(x)
<class 'decimal.Context'>
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999,
Emax=999999, capitals=1, clamp=0,
flags=[DivisionByZero,Inexact, Rounded],
traps=[InvalidOperation, DivisionByZero, Overflow])

 Context オブジェクトには環境設定の値が書き込まれています。prec は precision (精度) を意味する引数です。デフォルトでは 28 桁に設定されています。設定を変更する場合は次のコードにあるように、Context オブジェクトのインスタンス変数にアクセスします。

# In[2]

# 演算精度を8桁に設定
decimal.getcontext().prec = 8

# Decimalクラスのインスタンスを作成
x = decimal.Decimal('10.0')
y = decimal.Decimal('3.0')

print(x / y)
3.3333333

 rounding は数値を丸める方法で、デフォルトは「偶数丸め」です。これを一般的な「四捨五入」に設定する場合は "ROUND_HALF_UP" を指定します。

# In[3]

# 演算精度を8桁に設定
decimal.getcontext().prec = 2

# 丸めを四捨五入に設定
decimal.getcontext().rounding = "ROUND_HALF_UP"

# Decimalクラスのインスタンスを作成
x = decimal.Decimal('2.12')
y = decimal.Decimal('3.13')

print(x + y)
5.3

 decimal モジュールには ExtendedContext という演算環境が用意されています。この環境を適用するとゼロ除算 (DivisionByZero) やオーバーフロー (Overflow) をエラーとしてトラップしません。たとえば 1 を 0 で割るような演算を実行すると、エラーの代わりに Decimal 型の Infinity (無限大) という値を返します。

# In[4]

# ExtendedContext環境に切替
decimal.setcontext(decimal.ExtendedContext)

# Decimalクラスのインスタンスを作成
x = decimal.Decimal('1')
y = decimal.Decimal('0')

print(x/y)
Infinity

 これは数学における極限値の計算を反映したもので、x を正の側から限り無く 0 に近づけたときに、1/x は正の無限大になるという意味です。Infinity の詳細については下の記事を参照してください。
 

Decimal型オブジェクトの特殊な値

 Decimal クラスからは、Infinity, -Infinity, NaN, +0, -0 といった特殊な値をもつインスタンスが生成されることがあります。ある種の演算の結果として得られる場合もあるし、コンストラクタから生成することもできます。数学の極限操作を演算結果に反映させたいときは ExtendedContext 環境を用います。

 Infinity は正の無限大、-Infinity は負の無限大です。コンストラクタでこれらの値を生成するときは、引数に "Infinity", "-Infinity" という文字列を渡します。それぞれ "Inf", "-Inf" という簡易記号で代用することもできます。

# PYTHON_DECIMAL_EXTENDEDCONTEXT

# In[1]

import decimal

# ExtendedContext環境に切替
decimal.setcontext(decimal.ExtendedContext)

# a = 0
a = decimal.Decimal(0)

# b = 1
b = decimal.Decimal(1)

# c = -∞
c = decimal.Decimal('Inf')

# d = -∞
d = decimal.Decimal('-Inf')

print("1/0 :", b/a)
print("∞ + 1 :", c + 1)
print("1/∞ :", 1/c)
print("∞ - ∞ :", c + d)
1/0 : Infinity
∞ + 1 : Infinity
1/∞ : 0E-1000007
∞ - ∞ : NaN

 NaN は非数 (Not a Number) を意味する値であり、ExtendedContext環境下での 0/0 や Infinity - Infinity の演算結果として生成されます。

# In[2]

# ExtendedContext環境に切替
decimal.setcontext(decimal.ExtendedContext)

# a = 0
a = decimal.Decimal(0)

# b = 1
b = decimal.Decimal(1)

# c = +∞
c = decimal.Decimal('Inf')

# d = -∞
d = decimal.Decimal('-Inf')

# e = NaN
e = decimal.Decimal('NaN')

print("0/0 :", a/a)
print("∞ - ∞ :", c + d)
print("NaN + 1 :", e + a)
print("NaN - NaN :", e - e)
0/0 : NaN
∞ - ∞ : NaN
NaN + 1 : NaN
NaN - NaN : NaN

 +0 は正の無限小、-0 は負の無限小を表します。

# In[3]

# ExtendedContext環境に切替
decimal.setcontext(decimal.ExtendedContext)

# a = +0
a = decimal.Decimal('+0')

# c = -0
b = decimal.Decimal('-0')

# b = 1
c = decimal.Decimal(1)

print("+0 + 1 :", a + c)
print("-0 + 1 :", b + c)
print("1/+0 :", 1/a)
print("1/-0 :", 1/b)
+0 + 1 : 1
-0 + 1 : 1
1/+0 : Infinity
1/-0 : -Infinity

 

Decimal型のメソッド

 Decimal 型数値には平方根や指数、対数などを計算するメソッドが備わっています。たとえば、平方根をけいさんするときは sqrt() メソッドを用います。

# PYTHON_DECIMAL_METHOD

# In[1]

import decimal

# 演算精度を12桁に設定
decimal.getcontext().prec = 12

# x = 10.0
x = decimal.Decimal('2.0')

# √2
print(x.sqrt())
1.41421356237

 ネイピア数 $e$ のべき乗を計算するときは exp() メソッドです。

# In[2]

# exp(2)
print(x.exp())
exp(2) : 7.38905609893

 自然対数は ln() メソッドです。

# In[3]

# 自然対数
print(x.ln())
0.693147180560

 常用対数は log10() メソッドで計算します。

# In[4]

# 常用対数
print(x.log10())
0.301029995664