6.3.8 Decimal FAQ

Q. decimal.Decimal('1234.5') などと打ち込むのは煩わしいのですが、 対話式インタプリタを使う際にタイプ量を少なくする方法はありませんか?

A. コンストラクタを1文字に縮める人もいるようです。

>>> D = decimal.Decimal
>>> D('1.23') + D('3.45')
Decimal("4.68")

Q. 小数点以下2桁の固定小数点数のアプリケーションの中で、いくつかの 入力が余計な桁を保持しているのでこれを丸めなければなりません。その他の ものに余計な桁はなくそのまま使えます。どのメソッドを使うのがいいでしょうか?

A. quantize() メソッドで固定した桁に丸められます。 Inexact トラップを設定しておけば、確認にも有用です。

>>> TWOPLACES = Decimal(10) ** -2       # Decimal('0.01') と同じ

>>> # 小数点以下2桁に丸める
>>> Decimal("3.214").quantize(TWOPLACES)
Decimal("3.21")

>>> # 小数点以下2桁を越える桁を保持していないことの確認
>>> Decimal("3.21").quantize(TWOPLACES, context=Context(traps=[Inexact]))
Decimal("3.21")

>>> Decimal("3.214").quantize(TWOPLACES, context=Context(traps=[Inexact]))
Traceback (most recent call last):
   ...
Inexact: Changed in rounding

Q. 正当な2桁の入力が得られたとして、その正当性をアプリケーション実行中も 変わらず保ち続けるにはどうすればいいでしょうか?

A. 加減算のような演算は自動的に固定小数点を守ります。その他の乗除算などは 小数点以下の桁を変えてしまいますので実行後は quantize() ステップ が必要です。

Q. 一つの値に対して多くの表現方法があります。 200200.0002E2.02E+4 は全て同じ値で違った精度の数です。これらをただ一つの 正規化された値に変換することはできますか?

A. normalize() メソッドは全ての等しい値をただ一つの表現に直します。

>>> values = map(Decimal, '200 200.000 2E2 .02E+4'.split())
>>> [v.normalize() for v in values]
[Decimal("2E+2"), Decimal("2E+2"), Decimal("2E+2"), Decimal("2E+2")]

Q. ある種の10進数値はいつも指数表記で表示されます。 指数表記以外の表示にする方法はありますか?

A. 値によっては、指数表記だけが有効桁数を表せる表記法なのです。 たとえば、 5.0E+35000 と表してしまうと、 値は変わりませんが元々の2桁という有効数字が反映されません。

Q. 普通の float を Decimal に変換できますか?

A. はい。どんな2進浮動小数点数も Decimal として正確に表現できます。 正確な変換は直感的に考えたよりも多い桁になることもありますので、 Inexact をトラップしたとすればそれはもっと精度を上げる 必要性があることを示しています。

def floatToDecimal(f):
    "浮動小数点数を情報の欠落無く Decimal に変換します"

    # float で表された数を仮数 (0.5 <= abs(m) < 1.0) と指数に(正確に)転
    # 換します。仮数を整数になるまで2倍し続けます。整数化した仮数と指数
    # を使って等価な Decimal を求めます。この手続きが正確に行なえなかっ
    # たら、精度を上げて再度同じことをします。

    mantissa, exponent = math.frexp(f)
    while mantissa != int(mantissa):
        mantissa *= 2.0
        exponent -= 1
    mantissa = int(mantissa)

    oldcontext = getcontext()
    setcontext(Context(traps=[Inexact]))
    try:
        while True:
            try:
               return mantissa * Decimal(2) ** exponent
            except Inexact:
                getcontext().prec += 1
    finally:
        setcontext(oldcontext)

Q. 上の floatToDecimal() はなぜモジュールに入っていないのですか?

A. 2進と10進の浮動小数点数を混ぜるようにアドバイスするべきかどうか疑問が あります。また、これを使うときには2進浮動小数点数の表示の問題を避けるように 注意しなければなりません。

>>> floatToDecimal(1.1)
Decimal("1.100000000000000088817841970012523233890533447265625")

Q. 複雑な計算の中で、精度不足や丸めの異常で間違った結果になっていない ことをどうやって保証すれば良いでしょうか?

A. decimal モジュールでは検算は容易です。一番良い方法は、大きめの精度や 様々な丸めモードで再計算してみることです。大きく異なった結果が出てきたら、 精度不足や丸めの問題や悪条件の入力、または数値計算的に不安定なアルゴリズム を示唆しています。

Q. コンテキストの精度は計算結果には適用されていますが入力には適用されて いないようです。様々に異なる精度の入力値を混ぜて計算する時に注意すべき ことはありますか?

A. はい。原則として入力値は正確であると見做しておりそれらの値を使った 計算も同様です。結果だけが丸められます。入力の強みは ``what you type is what you get'' (打ち込んだ値が得られる値)という点にあります。 入力が丸められないということを忘れていると結果が奇妙に見えるというのは 弱点です。

>>> getcontext().prec = 3
>>> Decimal('3.104') + D('2.104')
Decimal("5.21")
>>> Decimal('3.104') + D('0.000') + D('2.104')
Decimal("5.20")

解決策は精度を上げるかまたは単項のプラス演算子を使って入力の丸めを強制する ことです。

>>> getcontext().prec = 3
>>> +Decimal('1.23456789')      # 単項のプラスで丸めを引き起こします
Decimal("1.23")

もしくは、入力を Context.create_decimal() を使って生成時に丸め てしまうこともできます。

>>> Context(prec=5, rounding=ROUND_DOWN).create_decimal('1.2345678')
Decimal("1.2345")
ご意見やご指摘をお寄せになりたい方は、 このドキュメントについて... をご覧ください。