1. ホーム
  2. python

[解決済み] float値を切り捨てるには?

2022-06-10 17:09:18

質問

浮動小数点以下の桁数が一定になるように桁上げを行いたい。

1.923328437452 → 1.923

printではなく、別の関数に文字列として出力したいのですが。

また、失われた数字を丸めるのではなく、無視したいです。

どのように解決するのですか?

まず、関数は、いくつかのコピーアンドペーストコードをしたい人のために。

def truncate(f, n):
    '''Truncates/pads a float f to n decimal places without rounding'''
    s = '{}'.format(f)
    if 'e' in s or 'E' in s:
        return '{0:.{1}f}'.format(f, n)
    i, p, d = s.partition('.')
    return '.'.join([i, (d+'0'*n)[:n]])

これはPython 2.7と3.1+で有効です。それ以前のバージョンでは、同じ "インテリジェント丸め" の効果を得ることはできません(少なくとも、多くの複雑なコードなしではできません)が、切り捨ての前に小数点以下12桁に丸めることは多くの場合うまくいきます。

def truncate(f, n):
    '''Truncates/pads a float f to n decimal places without rounding'''
    s = '%.12f' % f
    i, p, d = s.partition('.')
    return '.'.join([i, (d+'0'*n)[:n]])

説明

基本的な方法の核心は、値を完全な精度で文字列に変換し、その後、必要な文字数以上のすべてを切り落とすことです。後者のステップは簡単で、文字列操作で行うことができます。

i, p, d = s.partition('.')
'.'.join([i, (d+'0'*n)[:n]])

または decimal モジュール

str(Decimal(s).quantize(Decimal((0, (1,), -n)), rounding=ROUND_DOWN))

最初のステップである文字列への変換は非常に困難です。なぜなら、浮動小数点数リテラル(つまり、ソースコードに書かれているもの)には、どちらも同じ2進数表現を生成し、かつ異なる切り捨てをすべきペアが存在するからです。例えば、0.3と0.2999999999998を考えてみましょう。もしあなたが 0.3 と書くと、コンパイラはこれをIEEE浮動小数点フォーマットを使って次のようなビット列にエンコードします(64ビット浮動小数点と仮定)。

0011111111010011001100110011001100110011001100110011001100110011

これは、IEEE floatとして正確に表現できる0.3に最も近い値です。しかし、もしあなたが 0.29999999999999998 と書くと、コンパイラはそれを という全く同じ値 . あるケースでは、次のように(1桁に)切り捨てることを意味しました。 0.3 のように切り捨てるのに対して、もう一方のケースでは、次のように切り捨てることを意味します。 0.2 のように切り詰められることを意味しますが、Pythonは1つの答えしか与えることができません。これはPythonの、いや、遅延評価のないあらゆるプログラミング言語の基本的な制限です。切り捨て関数はコンピュータのメモリに保存されたバイナリ値にしかアクセスできず、実際にソースコードに入力された文字列にはアクセスできません。 1

IEEE の 64 ビット浮動小数点フォーマットを使用して、ビットのシーケンスを 10 進数にデコードすると、次のようになります。

0.2999999999999999888977697537484345957637...

ということで、素朴な実装では、以下のようになります。 0.2 と表示されます。浮動小数点数表示のエラーについて詳しくは Pythonチュートリアルを参照してください。 .

丸い数に近い浮動小数点値を扱うことは非常に稀であり、なおかつ 意図的に である浮動小数点値を扱うことは非常に稀です。ですから、切り捨てるときには、メモリ上の値に対応するすべてのものの中から、quot;nice" の10進表現を選ぶことがおそらく理にかなっています。Python 2.7 以降 (3.0 ではありません) には という洗練されたアルゴリズムがあります。 があり、デフォルトの文字列フォーマット操作でアクセスすることができます。

'{}'.format(f)

唯一の注意点は、これはまるで g のフォーマット指定と同じように動作します。 1.23e+4 )を使うという意味で、この書式指定は、数値が十分に大きいか小さいかの違いだけです。そこで、メソッドはこのケースをキャッチして、別の方法で処理しなければならない。を使うケースがいくつかあります。 f を切り詰めようとするような場合です。 3e-10 を28桁の精度で切り詰めようとすると(これは 0.0000000002999999999999999980 を生成します)、それらをどのように処理するのがベストなのかまだわかりません。

もしあなたが実際に で作業しているのであれば float を使用している場合、丸めた数に非常に近いが意図的に等しくないもの (0.29999999998 や 99.95999999994 など) は、いくつかの誤検出を生じます。そのような場合の解決策は、固定精度を指定することです。

'{0:.{1}f}'.format(f, sys.float_info.dig + n + 2)

ここで使用する精度の桁数は実際には重要ではなく、文字列変換で実行される丸めによって値が素敵な10進表現に "bump up" されないようにするために十分大きくする必要があるにすぎません。私は sys.float_info.dig + n + 2 で十分な場合もありますが、そうでない場合は 2 を増やさなければならないかもしれませんし、増やしておいて損はないでしょう。

Python の以前のバージョン (2.6 または 3.0 まで) では、浮動小数点数のフォーマットはもっと粗雑で、定期的に次のようなものを生成していました。

>>> 1.1
1.1000000000000001

もしこれがあなたの状況なら、もし する が切り捨てのために 10 進数表現を使いたい場合、(私の知る限り) できるのは float で表現できる完全な精度よりも低い桁数を選んで、切り捨てる前にその桁数に丸めることです。典型的な選択肢は 12 です。

'%.12f' % f

となっていますが、これは使用する数値に合わせて調整してください。


1 まあ... 私は嘘をつきました。厳密には、あなたは ができます。 は、Pythonに自分自身のソースコードを再解析して、切り捨て関数に渡す最初の引数に対応する部分を抽出するように指示します。もしその引数が浮動小数点数のリテラルであれば、小数点以下のある桁数で切り捨ててそれを返せばいいだけです。しかし、この方法は引数が変数の場合はうまくいきませんので、かなり無駄があります。以下は娯楽としてのみ紹介します。

def trunc_introspect(f, n):
    '''Truncates/pads the float f to n decimal places by looking at the caller's source code'''
    current_frame = None
    caller_frame = None
    s = inspect.stack()
    try:
        current_frame = s[0]
        caller_frame = s[1]
        gen = tokenize.tokenize(io.BytesIO(caller_frame[4][caller_frame[5]].encode('utf-8')).readline)
        for token_type, token_string, _, _, _ in gen:
            if token_type == tokenize.NAME and token_string == current_frame[3]:
                next(gen) # left parenthesis
                token_type, token_string, _, _, _ = next(gen) # float literal
                if token_type == tokenize.NUMBER:
                    try:
                        cut_point = token_string.index('.') + n + 1
                    except ValueError: # no decimal in string
                        return token_string + '.' + '0' * n
                    else:
                        if len(token_string) < cut_point:
                            token_string += '0' * (cut_point - len(token_string))
                        return token_string[:cut_point]
                else:
                    raise ValueError('Unable to find floating-point literal (this probably means you called {} with a variable)'.format(current_frame[3]))
                break
    finally:
        del s, current_frame, caller_frame

変数に値を与える浮動小数点数リテラルを見つけるまで、プログラムの実行をさかのぼらなければならないからです。それがあればの話ですが。ほとんどの変数は、ユーザー入力または数式から初期化され、その場合、バイナリ表現がすべてとなります。