十進浮動小数点数型 (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クラスのインスタンスを使って実行してみましょう。

# https://python.atelierkobato.com/decimal/

# 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("0.1 + 0.1 + 0.1 - 0.3 =", x + x + x - y)
<class 'decimal.Decimal'>
0.1 + 0.1 + 0.1 - 0.3 = 0.0

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

# https://python.atelierkobato.com/decimal/

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

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

print(x)
print(y)
1.3000000000000000444089209850062616169452667
1

 

Decimal型数値の演算

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

# https://python.atelierkobato.com/decimal/

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

# Decimalクラスのインスタンスを作成
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 mod 1.20 =", x % y)

# 1.20 + 1.30 のクラスを表示
print(type(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
<class 'decimal.Decimal'>

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

# https://python.atelierkobato.com/decimal/

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

# 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) を表示させることができます。

# https://python.atelierkobato.com/decimal/

# decimalモジュールをインポート
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オブジェクトのインスタンス変数(データ属性)にアクセスします。

# https://python.atelierkobato.com/decimal/

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

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

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

print("10.0/3.0 =", x / y)
10.0/3.0 =  3.3333333

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

# https://python.atelierkobato.com/decimal/

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

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

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

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

print("2.12 + 3.13 =", x + y)
2.12 + 3.13 = 5.3

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

# https://python.atelierkobato.com/decimal/

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

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

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

print("1/0 =", x/y)
1/0 = Infinity

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

カラー図解 最新 Raspberry Piで学ぶ電子工作 作って動かしてしくみがわかる (ブルーバックス)

新品価格
¥1,296から
(2019/8/21 23:40時点)

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

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

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

# https://python.atelierkobato.com/decimal/

# decimalモジュールをインポート
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 の演算結果として生成されます。

# https://python.atelierkobato.com/decimal/

# decimalモジュールをインポート
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')

# 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 は負の無限小を表します。

# https://python.atelierkobato.com/decimal/

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

# 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型数値には平方根や指数、対数などを計算するメソッドが備わっています。

# https://python.atelierkobato.com/decimal/

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

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

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

# √2
print("√2 =", x.sqrt())

# exp(2)
print("exp(2) =", x.exp())

# 自然対数
print("ln(2) =", x.ln())

# 常用対数
print("log(2) =", x.log10())
√2 = 1.41421356237
exp(2) = 7.38905609893
ln(2) = 0.693147180560
log(2) = 0.301029995664