web-dev-qa-db-ja.com

Pythonでのフロートの切り捨て

私は、フロートから数字を削除して、ドットの後に次のように固定数の数字を付けたいと思います:

1.923328437452 -> 1.923

印刷ではなく、別の関数に文字列として出力する必要があります。

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

93
Joan Venge

まず、コピーアンドペーストコードが必要な人のための関数:

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))

文字列に変換する最初のステップは、同じバイナリ表現を生成し、異なる切り捨てが必要な浮動小数点リテラルのペア(つまり、ソースコードで記述したもの)があるため、非常に困難です。たとえば、0.3と0.29999999999999998を考えます。 Pythonプログラムで0.3を記述する場合、コンパイラーはIEEE浮動小数点形式を使用してビットシーケンスにエンコードします(64ビットの浮動小数点を想定)

0011111111010011001100110011001100110011001100110011001100110011

これは、0.3に最も近い値で、IEEEフロートとして正確に表すことができます。ただし、Pythonプログラムで0.29999999999999998を記述すると、コンパイラはそれをまったく同じ値に変換します。 1つのケースでは、0.3として(1桁に)切り捨てられることを意味しましたが、別のケースでは、0.2として切り捨てられることを意味しましたが、Pythonは一つ答えてください。これは、Python、または実際に遅延評価のないプログラミング言語の基本的な制限です。切り捨て関数は、ソースコードに実際に入力した文字列ではなく、コンピューターのメモリに格納されているバイナリ値にのみアクセスできます。1

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

0.2999999999999999888977697537484345957637...

そのため、おそらくあなたが望んでいないものであっても、単純な実装は0.2を思い付きます。浮動小数点表現エラーの詳細については、 Pythonチュートリアルを参照

ラウンド数に非常に近いが、そのラウンド数と等しくない意図的にである浮動小数点値で作業することは非常にまれです。そのため、切り捨てるときは、メモリ内の値に対応する可能性のあるすべての「最も近い」10進表現を選択することはおそらく意味があります。 Python 2.7以降(3.0ではありません)には まさにそれを行う高度なアルゴリズム が含まれており、デフォルトの文字列フォーマット操作でアクセスできます。

'{}'.format(f)

唯一の注意点は、数値が十分に大きいか小さい場合に指数表記(1.23e+4)を使用するという意味で、g形式仕様のように機能することです。したがって、メソッドはこのケースをキャッチして、異なる方法で処理する必要があります。代わりにf形式仕様を使用すると、3e-10を28桁の精度に切り捨てようとする(0.0000000002999999999999999980を生成する)などの問題が発生する場合がありますが、まだそれらを処理するための最良の方法を確認してください。

実際にareが、ラウンド数に非常に近いが意図的にそれらに等しくないfloats(0.29999999999999998や99.959999999999994など)で作業している場合、これはいくつかの誤検知が発生します。つまり、丸めたくない数値を丸めます。その場合の解決策は、固定精度を指定することです。

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

ここで使用する精度の桁数は実際には重要ではありません。文字列変換で実行される丸めが値をそのニースの10進表現に「バンプアップ」しないようにするために必要なだけです。 sys.float_info.Dig + n + 2はすべての場合に十分かもしれませんが、そうでない場合は2を増やす必要があるかもしれません。そうすることは害になりません。

Pythonの以前のバージョン(最大2.6または3.0)では、浮動小数点数の書式設定はより粗雑で、次のようなものを定期的に生成していました。

>>> 1.1
1.1000000000000001

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

'%.12f' % f

使用している数字に合わせて調整できます。


1まあ...嘘をついた。技術的には、canを指定して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

変数を渡すケースを処理するためにこれを一般化すると、変数に値を与えた浮動小数点リテラルを見つけるまでプログラムの実行を逆方向にトレースする必要があるため、失われた原因のように見えます。さえあれば。ほとんどの変数は、ユーザー入力または数式から初期化されます。この場合、バイナリ表現がすべてです。

104
David Z
round(1.923328437452, 3)

標準タイプに関するPythonのドキュメント を参照してください。ラウンド機能を使用するには、少し下にスクロールする必要があります。基本的に、2番目の数字は、小数点以下の桁数を示します。

142
Teifion

roundの結果はfloatなので、注意してください(例はPython 2.6からです):

>>> round(1.923328437452, 3)
1.923
>>> round(1.23456, 3)
1.2350000000000001

書式設定された文字列を使用する場合は、より良い結果が得られます。

>>> "%.3f" % 1.923328437452
'1.923'
>>> "%.3f" % 1.23456
'1.235'
30
Ferdinand Beyer
n = 1.923328437452
str(n)[:4]
19
john_dough

私のPython 2.7プロンプトで:

>>> int(1.923328437452 * 1000)/1000.0 1.923

12
Bill

単純なpythonスクリプト-

n = 1.923328437452
n = float(int(n * 1000))
n /=1000
10
markroxor
def trunc(num, digits):
   sp = str(num).split('.')
   return '.'.join([sp[0], sp[1][:digits]])

これは動作するはずです。それはあなたが探している切り捨てを与えるべきです。

9
Matt

本当にPythonicな方法は

from decimal import *

with localcontext() as ctx:
    ctx.rounding = ROUND_DOWN
    print Decimal('1.923328437452').quantize(Decimal('0.001'))

以下:

from decimal import D, ROUND_DOWN

D('1.923328437452').quantize(D('0.001'), rounding=ROUND_DOWN)

更新

通常、問題は浮動小数点数自体の切り捨てではなく、浮動小数点数の不適切な使用before丸めにあります。

例:int(0.7*3*100)/100 == 2.09

フロートを使用することを強制している場合(たとえば、numbaでコードを加速している場合)、価格の「内部表現」としてセントを使用することをお勧めします:( 70*3 == 210)および入力/出力を乗算/除算します。

8

この質問に対して与えられた答えの多くはまったく間違っています。 (切り捨てではなく)浮動小数点数を切り上げるか、すべての場合に機能しません。

これは、 'Python truncate float'を検索したときのGoogleの最高の結果です。これは、本当に簡単で、より良い答えに値する概念です。私はdecimalモジュールを使用することがこれを行うPythonの方法であるとHatchkinsに同意します。そこで、質問に正しく答え、すべての場合に期待通りに機能すると思う関数をここに示します。

サイドノートとして、一般に、小数値はバイナリ浮動小数点変数で正確に表すことができません(これについては here を参照してください)。これが私の関数が文字列を返す理由です。

from decimal import Decimal, localcontext, ROUND_DOWN

def truncate(number, places):
    if not isinstance(places, int):
        raise ValueError("Decimal places must be an integer.")
    if places < 1:
        raise ValueError("Decimal places must be at least 1.")
    # If you want to truncate to 0 decimal places, just do int(number).

    with localcontext() as context:
        context.rounding = ROUND_DOWN
        exponent = Decimal(str(10 ** - places))
        return Decimal(str(number)).quantize(exponent).to_eng_string()
8
nullstellensatz

私はこのようなことをしました:

from math import trunc


def truncate(number, decimals=0):
    if decimals < 0:
        raise ValueError('truncate received an invalid value of decimals ({})'.format(decimals))
    Elif decimals == 0:
        return trunc(number)
    else:
        factor = float(10**decimals)
        return trunc(number*factor)/factor
4
Alvaro

できるよ:

def truncate(f, n):
    return math.floor(f * 10 ** n) / 10 ** n

テスト:

>>> f=1.923328437452
>>> [truncate(f, n) for n in range(5)]
[1.0, 1.9, 1.92, 1.923, 1.9233]
4
user648852

いくつかの数学をお望みなら、これは+ veの数値で機能します:

>>> v = 1.923328437452
>>> v - v % 1e-3
1.923
3
cs95

numpy.roundを使用

import numpy as np
precision = 3
floats = [1.123123123, 2.321321321321]
new_float = np.round(floats, precision)
1
rafaelvalle

int(16.5);これにより、16の整数値、つまりtruncが得られますが、小数を指定することはできませんが、

import math;

def trunc(invalue, digits):
    return int(invalue*math.pow(10,digits))/math.pow(10,digits);
1
Pieter

使用する一般的で単純な関数:

def truncate_float(number, length):
    """Truncate float numbers, up to the number specified
    in length that must be an integer"""

    number = number * pow(10, length)
    number = int(number)
    number = float(number)
    number /= pow(10, length)
    return number
1
Yohan Obadia

簡単な方法を次に示します。

def truncate(num, res=3):
    return (floor(num*pow(10, res)+0.5))/pow(10, res)

num = 1.923328437452の場合、これは1.923を出力します

1
Sarang
def trunc(f,n):
  return ('%.16f' % f)[:(n-16)]
1
Ross Cartlidge

古い「floor()でround()を作る」トリックが

round(f) = floor(f+0.5)

round()からfloor()を作成するために向きを変えることができます

floor(f) = round(f-0.5)

これらのルールは両方とも負の数を回避しますが、それを使用することは理想的ではありません。

def trunc(f, n):
    if f > 0:
        return "%.*f" % (n, (f - 0.5*10**-n))
    Elif f == 0:
        return "%.*f" % (n, f)
    Elif f < 0:
        return "%.*f" % (n, (f + 0.5*10**-n))
1
itsadok
def precision(value, precision):
    """
    param: value: takes a float
    param: precision: int, number of decimal places
    returns a float
    """
    x = 10.0**precision
    num = int(value * x)/ x
    return num
precision(1.923328437452, 3)

1.923

1
Andrew Olson

私の意見では、ほとんどの答えはあまりにも複雑です。これについてはどうですか?

digits = 2  # Specify how many digits you want

fnum = '122.485221'
truncated_float = float(fnum[:fnum.find('.') + digits + 1])

>>> 122.48

「。」のインデックスをスキャンするだけです必要に応じて切り捨てます(丸めなし)。最終ステップとして文字列を浮動小数点に変換します。

または、入力としてフロートを取得し、出力として文字列が必要な場合:

fnum = str(122.485221)  # convert float to string first
truncated_float = fnum[:fnum.find('.') + digits + 1]  # string output
1
Henry
>>> floor((1.23658945) * 10**4) / 10**4
1.2365

10 **希望する桁数で除算および乗算

0
JohnA

短くて簡単なバリアント

def truncate_float(value, digits_after_point=2):
    pow_10 = 10 ** digits_after_point
    return (float(int(value * pow_10))) / pow_10

>>> truncate_float(1.14333, 2)
>>> 1.14

>>> truncate_float(1.14777, 2)
>>> 1.14


>>> truncate_float(1.14777, 4)
>>> 1.1477
0
megajoe

here で与えられた核となるアイデアは、この問題に対する最良のアプローチであるように思えます。残念ながら、より多くの票を獲得している 後の回答 は完了していませんが、票数は少なくなっています(コメントで確認)。うまくいけば、以下の実装が短いandを提供しますtruncation =。

def trunc(num, digits):
    l = str(float(num)).split('.')
    digits = min(len(l[1]), digits)
    return (l[0]+'.'+l[1][:digits])

here および here で見つかったすべてのコーナーケースを処理する必要があります。

0
aak318

pandas dfを使用するとき、これは私のために働いた

import math
def truncate(number, digits) -> float:
    stepper = 10.0 ** digits
    return math.trunc(stepper * number) / stepper

df['trunc'] = df['float_val'].apply(lambda x: truncate(x,1))
df['trunc']=df['trunc'].map('{:.1f}'.format)
0
bart cubrich

python 3には簡単な回避策があります。カットする場所は、ヘルプ変数decPlaceで定義して、簡単に適応できるようにします。

f = 1.12345
decPlace= 4
f_cut = int(f * 10**decPlace) /10**decPlace

出力:

f = 1.1234

それが役に立てば幸い。

0
MBreg

ライブラリまたは他の外部依存関係なしで、リスト内包に収まるほど単純なもの。 Python> = 3.6の場合、f文字列を使用して記述するのは非常に簡単です。

考え方は、文字列変換に丸めを行わせることです必要以上の場所にし、最後の桁を切り捨てます。

>>> nout = 3  # desired number of digits in output
>>> [f'{x:.{nout+1}f}'[:-1] for x in [2/3, 4/5, 8/9, 9/8, 5/4, 3/2]]
['0.666', '0.800', '0.888', '1.125', '1.250', '1.500']

もちろん、ここでis丸めが発生します(つまり4桁目)が、丸めある時点では避けられません。切り捨てと丸めの間の移行が関連している場合、ここに少し良い例があります:

>>> nacc = 6  # desired accuracy (maximum 15!)
>>> nout = 3  # desired number of digits in output
>>> [f'{x:.{nacc}f}'[:-(nacc-nout)] for x in [2.9999, 2.99999, 2.999999, 2.9999999]]
>>> ['2.999', '2.999', '2.999', '3.000']

ボーナス:右側のゼロを削除する

>>> nout = 3  # desired number of digits in output
>>> [f'{x:.{nout+1}f}'[:-1].rstrip('0') for x in [2/3, 4/5, 8/9, 9/8, 5/4, 3/2]]
['0.666', '0.8', '0.888', '1.125', '1.25', '1.5']
0
Axel