ベスパリブ

プログラミングを主とした日記・備忘録です。ベスパ持ってないです。

小数点以下の不要な0を除去したい(Python)

何がやりたいかと端的に言うと、

  • 30.100 => 30.1
  • 30.000 => 30

のようにしたい。

Decimalモジュールを使う

小数点といえばDecimalなので、Decimalモジュールを探したらそれっぽいDecimal.normalizeがありました。

数値を正規化 (normalize) して、右端に連続しているゼロを除去し、 Decimal('0') と同じ結果はすべて Decimal('0e0') に変換します。等価クラスの属性から基準表現を生成する際に用います。たとえば、 Decimal('32.100') と Decimal('0.321000e+2') の正規化は、いずれも同じ値 Decimal('32.1') になります。

これは良さそう。早速使ってみます。

from decimal import Decimal
s = "30.0"
a = Decimal.normalize(Decimal(s))
print(a)
# 3E+1

うーん。指数表現になってしまった。

他に使えそうなものを探すと、Context.normalizeがありました。こっちを使ってみましょう。

from decimal import Decimal, Context

s = "30.0"

context = Context()
a = context.normalize(Decimal(s))
print(a)
#3E+1

変わらないですね。指数表現じゃなくしてほしいんですが。

色々漁ってると、Decimal FAQにドンピシャなQAがありました。

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

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

もしアプリケーションが有効数字の追跡を等閑視するならば、指数部や末尾のゼロを取り除き、有効数字を忘れ、しかし値を変えずにおくことは容易です:

>>> def remove_exponent(d):
...     return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
>>> remove_exponent(Decimal('5E+3'))
Decimal('5000')

なるほど。

結論

というわけで、数値(float or int)から正規化した文字列(str)を返す関数は以下のようになります。

from decimal import Decimal, Context


def decimal_normalize(f):
    """数値fの小数点以下を正規化し、文字列で返す"""
    def _remove_exponent(d):
        return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
    a = Decimal.normalize(Decimal(str(f)))
    b = _remove_exponent(a)
    return str(b)

print(decimal_normalize(30.0))
# 30
print(decimal_normalize(30))
# 30
print(decimal_normalize(30.0001000))
# 30.0001
print(decimal_normalize(0.0))
# 0
print(decimal_normalize(0))
# 0
print(decimal_normalize(-5.0))
# -5

Decimal(x)するときに、xはstr型にする必要はなくてfloat型のままでもいけますが、私はstr型に変換するようにしています。なんか挙動の差があって、str型に変換したほうが安定していたイメージがあるので(うろおぼえ)。

Decimalモジュールを使わない

Decimalモジュールを探す前に書いていたコード。Decimalを使いたくないときはこっちでもまあ。

def decimal_normalize(f):
    text = str(f)
    while True:
        if ("." in text and text[-1] == "0") or (text[-1] == "."):
            text = text[:-1]
            continue
        break
    return text

print(decimal_normalize(30.0))
# 30
print(decimal_normalize(30))
# 30
print(decimal_normalize(30.0001000))
# 30.0001
print(decimal_normalize(0.0))
# 0
print(decimal_normalize(0))
# 0
print(decimal_normalize(-5.0))
# -5