やっていくログ

にんげんをやっていきましょう

【Python】Decimal 型数値の quantize における str 関数の使用上の注意

Abstract

Python の Decimal 型数値を丸める quantize において、quantize のケタ数を指示するための Decimal 型数値の作成に str 関数を使用すると、期待した丸めにならない場合がある。それは、1, 10, …… の位で丸めたい場合である。
str 関数を使わず、クォーテーションで文字列とするのが安全。
また、ある変数を参照して文字列化したものを quantize のケタ数指示に用いる場合は、文字列化の際に、format で有効数字の指定を行うとよい。

Result

例えば、小数第1, 2, ……位で丸めたい場合、以下のように、str 関数を用いても、クォーテーションを用いても、同じ結果が得られる。

>>> decimal.Decimal('562.79').quantize(decimal.Decimal(str(1E-1)), rounding='ROUND_UP')
Decimal('562.8')

>>> decimal.Decimal('562.79').quantize(decimal.Decimal('1E-1'), rounding='ROUND_UP')
Decimal('562.8')


しかし、1, 10, ……の位で丸めたい場合、str 関数を用いると所望の結果は得られず、クォーテーションを用いると所望の結果が得られる。

>>> decimal.Decimal('562.79').quantize(decimal.Decimal(str(1E0)), rounding='ROUND_UP')
Decimal('562.8')

>>> decimal.Decimal('562.79').quantize(decimal.Decimal('1E0'), rounding='ROUND_UP')
Decimal('563')

>>> decimal.Decimal('562.79').quantize(decimal.Decimal(str(1E1)), rounding='ROUND_UP')
Decimal('562.8')

>>> decimal.Decimal('562.79').quantize(decimal.Decimal('1E1'), rounding='ROUND_UP')
Decimal('570')


これは、str 関数を用いたときと、クォーテーションを用いたときとで、内部での扱われ方が異なるため。
DecimalTuple(sign, digittuple, exponent) を比較すると、以下のようになっている。

>>> decimal.Decimal(str(1E1)).as_tuple()
DecimalTuple(sign=0, digits=(1, 0, 0), exponent=-1)

>>> decimal.Decimal('1E1').as_tuple()
DecimalTuple(sign=0, digits=(1,), exponent=1)

>>> decimal.Decimal(str(1E0)).as_tuple()
DecimalTuple(sign=0, digits=(1, 0), exponent=-1)

>>> decimal.Decimal('1E0').as_tuple()
DecimalTuple(sign=0, digits=(1,), exponent=0)


なお、1E-1以下では、どちらでも同じ扱われ方になっている。

>>> decimal.Decimal(str(1E-1)).as_tuple()
DecimalTuple(sign=0, digits=(1,), exponent=-1)

>>> decimal.Decimal('1E-1').as_tuple()
DecimalTuple(sign=0, digits=(1,), exponent=-1)


変数を受け取って、その変数に基づき quantize のケタを指示したい場合は、format において有効数字を 1 ケタにするとよい。そうすると、所望の結果が得られる。

>>> a = 1E2                                                                                                                                                                                      
>>> decimal.Decimal('562.79').quantize(decimal.Decimal('{:.1g}'.format(a)), rounding = 'ROUND_UP')                                                                                               
Decimal('6E+2')

>>> b = 1E1                                                                                                                                                                                      
>>> decimal.Decimal('562.79').quantize(decimal.Decimal('{:.1g}'.format(b)), rounding = 'ROUND_UP')                                                                                               
Decimal('5.7E+2')

>>> c = 1E0
>>> decimal.Decimal('562.79').quantize(decimal.Decimal('{:.1g}'.format(c)), rounding = 'ROUND_UP')                                                                                               
Decimal('563')

>>> d = 1E-1
>>> decimal.Decimal('562.79').quantize(decimal.Decimal('{:.1g}'.format(d)), rounding = 'ROUND_UP')                                                                                               
Decimal('562.8')