Pythonで数値を有効数字に丸める方法


148

UIに表示するフロートを丸める必要があります。たとえば、1つの重要な数値に:

1234-> 1000

0.12-> 0.1

0.012-> 0.01

0.062-> 0.06

6253-> 6000

1999-> 2000

Pythonライブラリを使用してこれを行う良い方法はありますか、それとも自分で作成する必要がありますか?


2
出力をフォーマットするだけですか?これについて質問していますか?docs.python.org/library/stdtypes.html#string-formattingまたはこれ? docs.python.org/library/string.html#string-formatting
S.Lott

0.062と6253にはどのような出力が予想されますか?
lamirap

パッケージto-precisionはこれを行います。私の投稿した回答は、これがどのように適用されるかを詳しく説明しています。
William Rusnack

回答:


146

負の数を使用して整数を丸めることができます。

>>> round(1234, -3)
1000.0

したがって、最上位桁のみが必要な場合:

>>> from math import log10, floor
>>> def round_to_1(x):
...   return round(x, -int(floor(log10(abs(x)))))
... 
>>> round_to_1(0.0232)
0.02
>>> round_to_1(1234243)
1000000.0
>>> round_to_1(13)
10.0
>>> round_to_1(4)
4.0
>>> round_to_1(19)
20.0

浮動小数点数が1より大きい場合は、浮動小数点数を整数に変換する必要があるでしょう。


3
これが正しい解決策です。log10丸める方法を決定するには、使用するのが唯一の適切な方法です。
Wolph

73
round_to_n = lambda x、n:round(x、-int(floor(log10(x)))+(n-1))
Roy Hyunjin Han

28
を使用する必要がありますlog10(abs(x))。そうでない場合、負の数は失敗します(x == 0もちろん、個別に扱います)
Tobias Kienzler '30

2
これを実行するパッケージを作成しました。おそらくこれよりも簡単で堅牢です。投稿リンクリポジトリリンク。お役に立てれば!
William Rusnack

2
round_to_n = lambda x, n: x if x == 0 else round(x, -int(math.floor(math.log10(abs(x)))) + (n - 1))@RoyHyunjinHanと@TobiasKienzler から保護しx==0x<0ありがとうございます。math.infのような未定義やNoneなどのゴミから保護されていません
AJP

98

文字列フォーマットの%gは、いくつかの有効数字に丸められた浮動小数点をフォーマットします。それは時々 'e'科学表記を使用するので、丸められた文字列を浮動小数点数に変換してから、%s文字列フォーマットを行います。

>>> '%s' % float('%.1g' % 1234)
'1000'
>>> '%s' % float('%.1g' % 0.12)
'0.1'
>>> '%s' % float('%.1g' % 0.012)
'0.01'
>>> '%s' % float('%.1g' % 0.062)
'0.06'
>>> '%s' % float('%.1g' % 6253)
'6000.0'
>>> '%s' % float('%.1g' % 1999)
'2000.0'

7
OPの要件は、1999年を「2000.0」ではなく「2000」としてフォーマットすることでした。これを実現するためにメソッドを変更する簡単な方法がわかりません。
Tim Martin

1
それは私がいつも欲しかったものです!どこで見つけたの?
djhaskin987 2013

12
%gの動作は常に正しいとは限らないことに注意してください。特に、たとえ後続のゼロが重要であっても常にそれ削除します。数値1.23400には6桁の有効数字がありますが、 "%。6g"%(1.23400)は "1.234"となり、これは正しくありません。このブログ投稿の詳細:randlet.com/blog/python-significant-figures-format
randlet

3
Evgenyの回答のメソッドと同様に、これはに正しく丸め0.075られません0.080.07代わりに戻ります。
ガブリエル

1
round_sig = lambda f,p: float(('%.' + str(p) + 'e') % f)有効桁数を調整できます!
denizb 2017

49

1桁以外の有効桁数にしたい場合(それ以外はEvgenyと同じ):

>>> from math import log10, floor
>>> def round_sig(x, sig=2):
...   return round(x, sig-int(floor(log10(abs(x))))-1)
... 
>>> round_sig(0.0232)
0.023
>>> round_sig(0.0232, 1)
0.02
>>> round_sig(1234243, 3)
1230000.0

8
round_sig(-0.0232)->数学ドメインエラー、そこにabs()を追加することができます;)
dgorissen

2
EvgenyとPeter Grahamの回答のメソッドと同様に、これはに正しく丸め0.075られません0.080.07代わりに戻ります。
ガブリエル

3
また、round_sig(0)では失敗します。
ユヴァル・アツモン2017

2
@Gabrielこれは、コンピューターで実行されているpythonの組み込み「機能」であり、関数の動作に現れますrounddocs.python.org/2/tutorial/floatingpoint.html#tut-fp-issues
初心者C

1
@Gabriel 「0.075」の四捨五入から0.7が返されること期待する理由を説明する回答を追加しました。stackoverflow.com/a/56974893/1358308を
Sam Mason

30
f'{float(f"{i:.1g}"):g}'
# Or with Python <3.6,
'{:g}'.format(float('{:.1g}'.format(i)))

このソリューションは他のすべてのものとは異なります。

  1. それはまさに OPの質問を解決します
  2. 追加のパッケージ必要ありませ
  3. ユーザー定義の補助関数数学演算必要ありませ

任意の数nの有効数字の場合、次を使用できます。

print('{:g}'.format(float('{:.{p}g}'.format(i, p=n))))

テスト:

a = [1234, 0.12, 0.012, 0.062, 6253, 1999, -3.14, 0., -48.01, 0.75]
b = ['{:g}'.format(float('{:.1g}'.format(i))) for i in a]
# b == ['1000', '0.1', '0.01', '0.06', '6000', '2000', '-3', '0', '-50', '0.8']

:このソリューションでは、末尾のゼロ(3.14 == 3.1400)の数が異なる数値を区別する標準的な方法がないため、入力から有効数字の数を動的に調整することはできません。そうする必要がある場合は、to-precisionパッケージで提供されるような非標準の関数が必要です。


参考までに、私のコードの1つで同じ問題を解決しようとしているときに、eddygeekとは別にこのソリューションを見つけました。私の解決策は明らかに彼の解決策とほとんど同じであることがわかりました(誤った出力に気付いただけで、コードを読んでもわからなかった、私の間違い)。多分彼の答えの下の短いコメントは新しい答えの代わりに十分だったでしょう...唯一の(重要な)違いは:g整数を保存するフォーマッターの二重の使用です。
ファルケン

うわー、あなたの答えは本当に上から下に読む必要があります;)このダブルキャストのトリックは汚いですが、きちんとしています。(1999 2000.0 5桁の有効桁数を示すようにフォーマットされているため、{:g}もう一度確認する必要があります。)一般に、末尾のゼロのある整数は、何らかの方法(最後の有効桁の上線など)を使用しない限り、有効桁数があいまいです。
Tomasz Gandor

8

私はあなたが望むことをするパッケージを精密に作成しました。それはあなたの数値に多かれ少なかれ重要な数字を与えることを可能にします。

また、指定された数の有効数字を含む標準、科学、および工学表記を出力します。

受け入れられた答えには、行があります

>>> round_to_1(1234243)
1000000.0

これは実際には8つのイチジクを指定しています。1234243という番号の場合、私のライブラリには重要な数値が1つしか表示されません。

>>> from to_precision import to_precision
>>> to_precision(1234243, 1, 'std')
'1000000'
>>> to_precision(1234243, 1, 'sci')
'1e6'
>>> to_precision(1234243, 1, 'eng')
'1e6'

また、最後の有効数字を丸め、表記法が指定されていない場合に使用する表記法を自動的に選択できます。

>>> to_precision(599, 2)
'600'
>>> to_precision(1164, 2)
'1.2e3'

今私は同じものを探していますが、パンダのdfに適用されています
mhoff

@mhoffおそらく、ラムダでパンダマップを使用できます。lambda x: to_precision(x, 2)
William Rusnack

これを(PyPI)[ pypi.org/]に追加します。私の知る限り、そこにはこのようなものはありません。
モルゴス

これは素晴らしいパッケージですが、ほとんどの機能はsigfigモジュール
HyperActive

1
バグがあります:std_notation(9.999999999999999e-05、3)は次の値を返します: '0.00010'これは有効数字が2桁しかない
ボリス・マルダー

5

整数を1桁の有効桁数に丸めるには、基本的な考え方は、小数点の前に1桁の浮動小数点に変換し、それを丸めてから、元の整数サイズに戻すことです。

これを行うには、整数よりも小さい10の最大の累乗を知る必要があります。これには、log 10関数のfloorを使用できます。

from math import log10, floor
def round_int(i,places):
    if i == 0:
        return 0
    isign = i/abs(i)
    i = abs(i)
    if i < 1:
        return 0
    max10exp = floor(log10(i))
    if max10exp+1 < places:
        return i
    sig10pow = 10**(max10exp-places+1)
    floated = i*1.0/sig10pow
    defloated = round(floated)*sig10pow
    return int(defloated*isign)

1
さらに、Pythonの丸め(..、数字)なしで機能し、文字列が接続されていないソリューション用の1つ!
スティーブロジャース

5

質問に直接答えるために、ここにR関数からの命名を使用した私のバージョンがあります

import math

def signif(x, digits=6):
    if x == 0 or not math.isfinite(x):
        return x
    digits -= math.ceil(math.log10(abs(x)))
    return round(x, digits)

この回答を投稿する主な理由は、「0.075」が0.08ではなく0.07に丸められるとコメントしているコメントです。これは、「初心者C」が指摘したように、有限精度と2進数表現の両方を持つ浮動小数点演算の組み合わせによるものです。実際に表現できる0.075に最も近い数値はわずかに小さいため、単純に予想されるよりも丸めが異なります。

これは、非10進浮動小数点演算の使用にも適用されることに注意してください。たとえば、CとJavaの両方に同じ問題があります。

より詳細に表示するために、Pythonに数値を「16進」形式でフォーマットするように要求します。

0.075.hex()

これにより、次のようになります0x1.3333333333333p-4。これを行う理由は、通常の10進表記は丸めを伴うことが多いため、コンピューターが実際に数値を「見る」方法ではないためです。この形式に慣れていない場合は、PythonのドキュメントC標準が参考になります。

これらの数値がどのように機能するかを示すために、次のようにして開始点に戻ることができます。

0x13333333333333 / 16**13 * 2**-4

印刷する必要があります0.07516**13これは、小数点の後に13桁の16進数2**-4があり、16進数の指数が2を底とするためです。

これで、フロートがどのように表現されるかについていくつかのアイデアがdecimal得られました。モジュールを使用して精度を高め、何が起こっているかを示すことができます。

from decimal import Decimal

Decimal(0x13333333333333) / 16**13 / 2**4

与える:0.07499999999999999722444243844うまくいけば、なぜround(0.075, 2)評価するのかを説明する0.07


1
これは、コードレベルで 0.075が0.07に切り捨てられる理由の素晴らしい説明ですが、私たち(物理科学では)は切り捨てずに常に切り上げるように教えられています。したがって、期待される動作は実際には結果として0.08になることですが、浮動小数点の精度の問題があります。
ガブリエル

1
あなたの混乱がどこにあるのかわかりません:0.075を入力すると、実際には(上記のように)〜0.07499を入力します。これは通常の数学規則に従って丸められます。0.075を表すことができるデータ型(10進浮動小数点など)を使用している場合、実際には0.08に丸める必要があります
Sam Mason

私は混乱していません。0.075と入力すると、実際には0.075と入力します。コード内の浮動小数点演算で何が起こっても、私は気にしません。
ガブリエル

@Gabriel:あなたがいる場合としていた故意に入力された0.074999999999999999、あなたが、その場合には得ることを期待しますか?
マークディキンソン

依存する@MarkDickinson。1つの有意な数値:0.07、2つ:0.075。
ガブリエル

4
def round_to_n(x, n):
    if not x: return 0
    power = -int(math.floor(math.log10(abs(x)))) + (n - 1)
    factor = (10 ** power)
    return round(x * factor) / factor

round_to_n(0.075, 1)      # 0.08
round_to_n(0, 1)          # 0
round_to_n(-1e15 - 1, 16) # 1000000000000001.0

うまくいけば、上記のすべての答えの中で最善を尽くします(それを1行のラムダとして置くことはできません;))。まだ探索していません。この回答を自由に編集してください:

round_to_n(1e15 + 1, 11)  # 999999999999999.9

4

負の数と小さい数(ゼロを含む)を処理するようにindgarのソリューションを変更しました。

from math import log10, floor
def round_sig(x, sig=6, small_value=1.0e-9):
    return round(x, sig - int(floor(log10(max(abs(x), abs(small_value))))) - 1)

なぜ単にテストしないのx == 0ですか?ワンライナーが好きなら、ただreturn 0 if x==0 else round(...)
pjvandehaar

2
@pjvandehaar、あなたは一般的なケースに正解であり、私はそれを入れるべきでした。さらに、私が実行する必要がある数値計算では、1e-15のような数値が時々得られます。このアプリケーションでは、2つの小さな数値(そのうちの1つはゼロの可能性があります)の比較が等しいと見なされるようにします。また、少数(1e-9、1e-15、または1e-300の場合もある)をゼロに丸めたい人もいます。
ryan281

1
面白い。説明してくれてありがとう。その場合、私はこのソリューションが本当に好きです。
pjvandehaar

@Morgothこれは興味深い難しい問題です。ご指摘のとおり、印刷された値には3桁の有効数字は表示されませんが、値は正しいです(例:)0.970 == 0.97f'{round_sig(0.9701, sig=3):0.3f}'ゼロを印刷したい場合など、他の印刷ソリューションを使用できると思います。
ryan281

3

文字列を使用せずに丸めたい場合は、上記のコメントに埋め込まれたリンクを見つけました。

http://code.activestate.com/lists/python-tutor/70739/

最高の私を打つ。その後、文字列フォーマット記述子を使用して印刷すると、妥当な出力が得られ、数値表現を他の計算目的に使用できます。

リンクのコードは、def、doc、およびreturnの3つのライナーです。これにはバグがあります。爆発する対数をチェックする必要があります。それは簡単だ。入力をと比較しますsys.float_info.min。完全なソリューションは次のとおりです。

import sys,math

def tidy(x, n):
"""Return 'x' rounded to 'n' significant digits."""
y=abs(x)
if y <= sys.float_info.min: return 0.0
return round( x, int( n-math.ceil(math.log10(y)) ) )

これは、任意のスカラー数値で機能しfloat、何らかの理由で応答をシフトする必要がある場合は、nをaにすることができます。あなたは実際に制限をプッシュすることができます:

sys.float_info.min*sys.float_info.epsilon

なんらかの理由で非常に小さな値で作業している場合、エラーを発生させることなく。


2

箱から出してこれを処理できるものは何も考えられません。しかし、浮動小数点数の場合はかなりうまく処理されます。

>>> round(1.2322, 2)
1.23

整数はトリッキーです。それらはベース10としてメモリに保存されないため、重要な場所を作成するのは自然なことではありません。ただし、文字列になったら実装するのは簡単です。

または整数の場合:

>>> def intround(n, sigfigs):
...   n = str(n)
...   return n[:sigfigs] + ('0' * (len(n)-(sigfigs)))

>>> intround(1234, 1)
'1000'
>>> intround(1234, 2)

任意の数値を処理する関数を作成したい場合、私の好みは、両方を文字列に変換し、小数点以下を探して何をすべきかを決定することです。

>>> def roundall1(n, sigfigs):
...   n = str(n)
...   try:
...     sigfigs = n.index('.')
...   except ValueError:
...     pass
...   return intround(n, sigfigs)

別のオプションはタイプをチェックすることです。これは柔軟性がはるかに低くなり、Decimalオブジェクトなどの他の数値ではうまく機能しない可能性があります。

>>> def roundall2(n, sigfigs):
...   if type(n) is int: return intround(n, sigfigs)
...   else: return round(n, sigfigs)

文字列をいじるだけでは、数値は丸められません。1999 1つの有効数字に丸められ、2000年ではない1000です
ピーター・グラハム


2

投稿された回答は、与えられた時点で最良のものでしたが、いくつかの制限があり、技術的に正しい有意な数値を生成しません。

numpy.format_float_positionalは、目的の動作を直接サポートします。次のフラグメントは、x科学的表記が抑制された4つの有効数字にフォーマットされた浮動小数点数を返します。

import numpy as np
x=12345.6
np.format_float_positional(x, precision=4, unique=False, fractional=False, trim='k')
> 12340.

ドキュメント(numpy.org/doc/stable/reference/generated/…に移動)には、この関数がDragon4アルゴリズム(Steele&White 1990のdl.acm.org/doi/pdf/10.1145/93542.93559)を実装することが記載されています。それは迷惑な結果を生成しprint(*[''.join([np.format_float_positional(.01*a*n,precision=2,unique=False,fractional=False,trim='k',pad_right=5) for a in [.99, .999, 1.001]]) for n in [8,9,10,11,12,19,20,21]],sep='\n')ます。Dragon4自体はチェックしませんでした。
Rainald62

0

私もこれに遭遇しましたが、丸めのタイプを制御する必要がありました。したがって、値、丸めタイプ、および必要な有効数字を考慮に入れることができるクイック関数(以下のコードを参照)を作成しました。

import decimal
from math import log10, floor

def myrounding(value , roundstyle='ROUND_HALF_UP',sig = 3):
    roundstyles = [ 'ROUND_05UP','ROUND_DOWN','ROUND_HALF_DOWN','ROUND_HALF_UP','ROUND_CEILING','ROUND_FLOOR','ROUND_HALF_EVEN','ROUND_UP']

    power =  -1 * floor(log10(abs(value)))
    value = '{0:f}'.format(value) #format value to string to prevent float conversion issues
    divided = Decimal(value) * (Decimal('10.0')**power) 
    roundto = Decimal('10.0')**(-sig+1)
    if roundstyle not in roundstyles:
        print('roundstyle must be in list:', roundstyles) ## Could thrown an exception here if you want.
    return_val = decimal.Decimal(divided).quantize(roundto,rounding=roundstyle)*(decimal.Decimal(10.0)**-power)
    nozero = ('{0:f}'.format(return_val)).rstrip('0').rstrip('.') # strips out trailing 0 and .
    return decimal.Decimal(nozero)


for x in list(map(float, '-1.234 1.2345 0.03 -90.25 90.34543 9123.3 111'.split())):
    print (x, 'rounded UP: ',myrounding(x,'ROUND_UP',3))
    print (x, 'rounded normal: ',myrounding(x,sig=3))

0

Python 2.6以降の新しいスタイルのフォーマットを使用(%スタイルは非推奨)。

>>> "{0}".format(float("{0:.1g}".format(1216)))
'1000.0'
>>> "{0}".format(float("{0:.1g}".format(0.00356)))
'0.004'

Python 2.7以降では、先頭0のを省略できます。


Pythonのバージョンは何ですか?Python 3.6.3 | Anaconda、Inc. | (デフォルト、2017年10月13日、12:02:49)には、同じ古い丸めの問題があります。"{0}"。format(float( "{0:.1g}"。format(0.075)))は、 '0.08'ではなく '0.07'を
返します


0

この関数は、数値が10 **(-decimal_positions)より大きい場合は通常の丸めを行い、それ以外の場合は、意味のある小数点以下の桁数に達するまで小数点を追加します。

def smart_round(x, decimal_positions):
    dp = - int(math.log10(abs(x))) if x != 0.0 else int(0)
    return round(float(x), decimal_positions + dp if dp > 0 else decimal_positions)

それが役に立てば幸い。


0

https://stackoverflow.com/users/1391441/gabriel、次はrnd(.075、1)に関する懸念に対処していますか?警告:浮動小数点数として値を返します

def round_to_n(x, n):
    fmt = '{:1.' + str(n) + 'e}'    # gives 1.n figures
    p = fmt.format(x).split('e')    # get mantissa and exponent
                                    # round "extra" figure off mantissa
    p[0] = str(round(float(p[0]) * 10**(n-1)) / 10**(n-1))
    return float(p[0] + 'e' + p[1]) # convert str to float

>>> round_to_n(750, 2)
750.0
>>> round_to_n(750, 1)
800.0
>>> round_to_n(.0750, 2)
0.075
>>> round_to_n(.0750, 1)
0.08
>>> math.pi
3.141592653589793
>>> round_to_n(math.pi, 7)
3.141593

0

これは文字列を返すため、小数部のない結果と、E表記で表示される小さな値が正しく表示されます。

def sigfig(x, num_sigfig):
    num_decplace = num_sigfig - int(math.floor(math.log10(abs(x)))) - 1
    return '%.*f' % (num_decplace, round(x, num_decplace))

0

質問が与えられたので、完全に答えたので、なぜ追加しない

上記の多くは同等ですが、これは私の美的感覚に少しよく合います

import numpy as np

number=-456.789
significantFigures=4

roundingFactor=significantFigures - int(np.floor(np.log10(np.abs(number)))) - 1
rounded=np.round(number, roundingFactor)

string=rounded.astype(str)

print(string)

これは、個々の数値とnumpy配列に対して機能し、負の数値に対しては正常に機能するはずです。

追加する可能性のある追加の手順が1つあります。np.round()は、丸めが整数であっても10進数を返します(つまり、有意な数値= 2の場合、-460が返されると予想されますが、代わりに-460.0が返されます)。これを修正するために、このステップを追加できます。

if roundingFactor<=0:
    rounded=rounded.astype(int)

残念ながら、この最後の手順は、一連の数値に対しては機能しません。必要な場合は、読者の皆さんにご説明します。


0

sigfigのパッケージ/ライブラリがこれをカバーしています。インストール後、次のことができます。

>>> from sigfig import round
>>> round(1234, 1)
1000
>>> round(0.12, 1)
0.1
>>> round(0.012, 1)
0.01
>>> round(0.062, 1)
0.06
>>> round(6253, 1)
6000
>>> round(1999, 1)
2000

0
import math

  def sig_dig(x, n_sig_dig):
      num_of_digits = len(str(x).replace(".", ""))
      if n_sig_dig >= num_of_digits:
          return x
      n = math.floor(math.log10(x) + 1 - n_sig_dig)
      result = round(10 ** -n * x) * 10 ** n
      return float(str(result)[: n_sig_dig + 1])


    >>> sig_dig(1234243, 3)
    >>> sig_dig(243.3576, 5)

        1230.0
        243.36
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.