Pythonでの浮動小数点数の切り捨て


110

次のように、浮動小数点数から数字を削除して、ドットの後に固定数の数字を配置したいと思います。

1.923328437452 -> 1.923

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

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


4
-1.233を-1.23または-1.24に切り捨てますか?
アントニーハッチキンズ2014年

回答:


116

最初に、いくつかのコピーアンドペーストコードが必要な人のための関数:

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を考えます。0.3Pythonプログラムで記述する場合、コンパイラーはIEEE浮動小数点形式を使用してそれをビットのシーケンスにエンコードします(64ビットの浮動小数点数を想定)。

0011111111010011001100110011001100110011001100110011001100110011

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

ビットのシーケンスをデコードして10進数に戻すと、再びIEEE 64ビット浮動小数点形式を使用して、

0.2999999999999999888977697537484345957637...

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

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

'{}'.format(f)

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

丸め数値に非常に近いs を実際に操作しいるfloatが、意図的にそれらと等しくない場合(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

これがあなたの状況で、切り捨てに「素敵な」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

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


この関数をデータフレームにどのように適用できますか?
CodeLord 2018

@RohithRNair頭の上から、個々の要素で動作する他の関数を適用するのと同じ方法(つまりapplymap())。操作全体をより効率的にする方法があるかもしれませんが、それは別の問題の問題でしょう。
デビッドZ

データフレームが本当に大きいので、applymap()は時間がかかります。2つのデータフレームの違いを比較しようとしていますが、浮動小数点の精度により、出力が望ましい値から歪んでいます。あなたが言ったように、私は同じことについて別の質問をします。ありがとう。
CodeLord 2018

@RohithRNairああ、2つのデータフレームの違いを比較しようとしている場合は、代わりにそれについて尋ねてください。値を切り捨てる(これはこの質問についてです)ことは、それを行うための最良の方法ではありません。
デビッドZ

ほんのメモ、あなたのコードは負の数を負のゼロに
切り刻んでいる

152
round(1.923328437452, 3)

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


49
丸めは私が必要とするものではないことを意味しました。切り捨てが必要ですが、これは異なります。
ジョーンベンジ、

1
ああ、十分に公平です。私の間違いはごめんなさい。
Teifion 2009

22
それは間違った解決策に対する多くの賛成票です!これらの奇妙なStackoverflowレアリティの1つ。そのためのバッジがあるのだろうか...
tumultous_rooster 14年

5
これは、この質問に対する不正解の数(および不正解への賛成票)がどれほど多くなるのかを恐ろしく思います。
nullstellensatz 2015

6
多くの人々が丸めを求めてこのページに
アクセスし

33

の結果roundは浮動小数点なので、注意してください(例は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'

8
私のPythonでは、それは丸められます: '%.3f'%1.23456 == '1.235'
David Z

これは、手動の文字列フォーマットのナンセンス、優れた投稿よりもずっとエレガントです!
rsethc

round(1.23456, 3)is 1.235and not1.2350000000000001
アフマド

1
@Ahmadとは限りません。ここの例はPython 2.6のものです(回答の日付に注意してください)。文字列の書式設定はPython 2.7 / 3.1で改善されたため、おそらく異なる結果が得られます。それにもかかわらず、浮動小数点数は予期しない文字列表現を持つことがよくあります。次を
Ferdinand Beyer

21
n = 1.923328437452
str(n)[:4]

3
シンプルでパイソン風。ただし、ドットの後の数字だけではなく、4は整数のサイズです。
GaTTaCa

4
たとえば2、ユーザーがと入力した場合、.文字列の最後に小数点が表示されます-本当に良い解決策ではないと思います。
Zelphir Kaltstahl 2016年

これは、この番号のケースに固有です。11.923328437452に一般化するとどうなりますか?
分極

ベストアンサー!float()を追加して数値を返すこともできます:float(str(n)[:4])
justSaid

14

私のPython 2.7プロンプトで:

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


11

単純なpythonスクリプト-

n = 1.923328437452
n = float(int(n * 1000))
n /=1000

3
きれいな答え。あなたはちょうどあなたが1を取得し、それ以外の場合は1000で割る前にフロートに戻って変換するには、一歩を欠場
ヨハンObadia

9
def trunc(num, digits):
   sp = str(num).split('.')
   return '.'.join([sp[0], sp[1][:digits]])

これはうまくいくはずです。それはあなたが探している切り詰めをあなたに与えるはずです。


9

それを行う本当にPythonの方法は

from decimal import *

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

以下:

from decimal import Decimal as D, ROUND_DOWN

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

更新

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

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

浮動小数点数を使用せざるを得ない場合(たとえば、でコードを高速化している場合numba)、セントを価格の「内部表現」として使用し70*3 == 210、入力/出力を乗算/除算することをお勧めします。


これを尋ねてくれたパーソン私、しかし...なぜ?
markroxor

@markroxor、あなたが正確に何を求めているのかわからない。補足として、通常、問題は丸め自体ではなく、丸めの浮動小数点数の不適切な使用にあります。例えばint(0.7*3*100)/100 == 2.09。私の1セントはどこに行きましたか?
アントニーハッチキンズ

それは理にかなっています、あなたはこの説明であなたの答えを編集できますか?ありがとう。
markroxor

ええとImportError: cannot import name 'D'、あなたは名前付きインポートを作りたくなかったと思いますか?
Overdrivr

8

この質問に対する答えの多くは、完全に間違っています。(切り捨てるのではなく)floatを切り上げるか、すべてのケースで機能するわけではありません。

これは、「Python truncate float」を検索したときのGoogleの検索結果のトップです。この概念は本当に簡単で、より良い答えに値します。decimalモジュールを使用することはこれを行うpythonicの方法であるというハッチキンスに同意するので、質問に正しく答えると思い、すべての場合に期待どおりに機能する関数をここに与えます。

補足として、一般的に、小数値は2進浮動小数点変数では正確に表すことができません(これについては、こちらを参照してください)。これが、関数が文字列を返す理由です。

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

4

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

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

できるよ:

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]

これは正の数でのみ切り捨てられ、負の数は切り捨てられます(ゼロから離れて)。
アーロンD

3

あなたがいくつかの数学を空想しているなら、これは+ ve数のために働きます:

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

私が理解しているように、1e-3はドットの後に3桁に切り捨てます。私はこの回答が好きでしたが、4と5では機能しないようです
egvo

2

パンダの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)

1

古い "make round()with floor()"トリックの

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

int(16.5); これは整数値16を提供します。つまり、truncは小数を指定できませんが、次の方法でそれができると思います

import math;

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

1

ここに簡単な方法があります:

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

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



1

使用する一般的でシンプルな関数:

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

すごい!intへのキャストは、正と負の両方の数値を切り捨てます。
アーロンD

1

Python 3には簡単な回避策があります。どこでカットするかは、適応を容易にするためにヘルプ変数decPlaceで定義しました。

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

出力:

f = 1.1234

それが役に立てば幸い。


1
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


いいですが、丸めません。
Alex

1

短くて簡単なバリエーション

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

1

私の意見ではほとんどの回答が複雑すぎるようですが、これはどうですか?

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

小数点の右側の先行0を使用すると精度が大幅に低下するため、切り捨てられる数値が小さい場合、提案は問題になります。しかし、この問題は前述の問題に固有のものです。私が言おうとしているのは、重要な数字が本当の答えだということです。
オーバーコイル

1
>>> floor((1.23658945) * 10**4) / 10**4
1.2365

必要な桁数の10 **で除算および乗算


0

numpy.roundを使用

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

0

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

アイデアは、文字列変換に必要以上の1桁に丸め実行させ、最後の桁を切り捨てることです。

>>> 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']

もちろん、ここで四捨五入が行われます(つまり、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

ここで与えられ核となる考えは、この問題に対する最良のアプローチであるように私には思われます。残念ながら、それはより少ない票を獲得しましたが、より多くの票を持つ後者の回答は完了していません(コメントで観察されるように)。うまくいけば、実装は以下の短い提供し、完全なソリューションの切り捨てを

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

ここここで見つかっすべてのコーナーケースを処理する必要があります


-1

python初心者でもあり、ここでいくつかの断片を使用した後、2セントを提供します

print str(int(time.time()))+str(datetime.now().microsecond)[:3]

str(int(time.time()))は、時間エポックをintとして受け取り、それを文字列に変換して結合します... str(datetime.now()。microsecond)[:3]これはマイクロ秒のみを返し、変換します文字列に変換し、最初の3文字に切り捨てます



-3

印刷する場合は、次のようにしてください。

print '%.3f' % number

2
数値は四捨五入され、切り捨てられません。
デビッドZ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.