私はround()
関数の奇妙な振る舞いに直面しています:
for i in range(1, 15, 2):
n = i / 2
print(n, "=>", round(n))
このコードは次を印刷します:
0.5 => 0
1.5 => 2
2.5 => 2
3.5 => 4
4.5 => 4
5.5 => 6
6.5 => 6
浮動値は常に切り上げられると予想していましたが、代わりに、最も近い偶数に切り上げられます。
なぜそのような動作であり、正しい結果を得るための最良の方法は何ですか?
fractions
を使用しようとしましたが、結果は同じです。
Numeric Typesセクション は、この動作を明示的に文書化しています:
round(x[, n])
xn桁に丸め、半分を偶数に丸めます。 nを省略すると、デフォルトで0になります。
半分に偶数に丸めることに注意してください。これは、bankers roundingとも呼ばれます。丸め誤差を平均する最も近いeven数に丸めることにより、常に切り上げまたは切り捨て(丸め誤差を合成)する代わりに。
丸め動作をさらに制御する必要がある場合は、 decimal
module を使用します。これにより、 丸め戦略を使用する必要がある を正確に指定できます。
たとえば、半分から切り上げるには:
>>> from decimal import localcontext, Decimal, ROUND_HALF_UP
>>> with localcontext() as ctx:
... ctx.rounding = ROUND_HALF_UP
... for i in range(1, 15, 2):
... n = Decimal(i) / 2
... print(n, '=>', n.to_integral_value())
...
0.5 => 1
1.5 => 2
2.5 => 3
3.5 => 4
4.5 => 5
5.5 => 6
6.5 => 7
例えば:
from decimal import Decimal, ROUND_HALF_UP
Decimal(1.5).quantize(0, ROUND_HALF_UP)
# This also works for rounding to the integer part:
Decimal(1.5).to_integral_value(rounding=ROUND_HALF_UP)
round()
は、数値が偶数か奇数かに応じて切り上げまたは切り捨てを行います。切り上げるだけの簡単な方法は次のとおりです。
int(num + 0.5)
これを負の数に対して適切に機能させるには、次を使用します。
((num > 0) - (num < 0)) * int(abs(num) + 0.5)
これは、大きな数字や、5000000000000001.0
および0.49999999999999994
。
これを使用できます:
import math
def normal_round(n):
if n - math.floor(n) < 0.5:
return math.floor(n)
return math.ceil(n)
数値を適切に切り上げまたは切り捨てます。
表示されている動作は、IEEE 754の一般的な丸め動作です。入力と等しく異なる2つの数値から選択する必要がある場合、常に偶数を選択します。この動作の利点は、平均の丸め効果がゼロであるということです。同じ数の数値が切り上げおよび切り捨てられます。一貫した方向に半分の数を丸めると、丸めは期待値に影響します。
目的が公平な丸めである場合、表示されている動作は正しいですが、それが常に必要なわけではありません。
必要な丸めの種類を取得するための1つのトリックは、0.5を追加してからフロアを取得することです。たとえば、0.5を2.5に追加すると、3が得られ、フロアが3になります。
短いバージョン: decimal module を使用します。 Python floatsで2.675がreally2.67499999999999982236431605997495353221893310546875(正確に)とは異なり、2.675のような数値を正確に表すことができます。必要な丸めを指定します。ROUND_CEILING、ROUND_DOWN、ROUND_FLOOR、ROUND_HALF_DOWN、ROUND_HALF_EVEN、ROUND_HALF_UP、ROUND_UP、ROUND_05UPはすべてオプションです。
fedor2612 の答えが大好きです。この関数を使用して任意の数の小数を丸めたい場合(たとえば、通貨を26.455ドルから26.46ドルに丸めたい場合など)に、オプションの「decimals」引数を追加しました。
import math
def normal_round(n, decimals=0):
expoN = n * 10 ** decimals
if abs(expoN) - abs(math.floor(expoN)) < 0.5:
return math.floor(expoN) / 10 ** decimals
return math.ceil(expoN) / 10 ** decimals
oldRounding = round(26.455,2)
newRounding = normal_round(26.455,2)
print(oldRounding)
print(newRounding)
出力:
26.45
26.46
数値の分野では、最も近い偶数への丸めが一般的になっています。 「端数処理」は、より大きな結果へのわずかな偏りを生み出します。
したがって、科学的確立の観点から見ると、round
の動作は正しいです。
別の解決策があります。 Excelでは通常の丸めとして機能します。
_from decimal import Decimal, getcontext, ROUND_HALF_UP
round_context = getcontext()
round_context.rounding = ROUND_HALF_UP
def c_round(x, digits, precision=5):
tmp = round(Decimal(x), precision)
return float(tmp.__round__(digits))
_
c_round(0.15, 1) -> 0.2, c_round(0.5, 0) -> 1
次の解決策は、decimal
モジュールを使用せずに「学校のファッションの丸め」を実現しました(これは遅いことが判明しました)。
def school_round(a_in,n_in):
''' python uses "banking round; while this round 0.05 up" '''
if (a_in * 10 ** (n_in + 1)) % 10 == 5:
return round(a_in + 1 / 10 ** (n_in + 1), n_in)
else:
return round(a_in, n_in)
例えば.
print(round(0.005,2)) # 0
print(school_round(0.005,2)) #0.01
質問では、これは基本的に正の整数を2で除算するときの問題です。最も簡単な方法は、個々の数値のint(n + 0.5)
です。
ただし、これをシリーズに適用することはできません。したがって、たとえばpandasデータフレームで、ループに入らずにできることは次のとおりです。
import numpy as np
df['rounded_division'] = np.where(df['some_integer'] % 2 == 0, round(df['some_integer']/2,0), round((df['some_integer']+1)/2,0))
次を使用できます。
from decimal import Decimal, ROUND_HALF_UP
for i in range(1, 15, 2):
n = i / 2
print(n, "=>", Decimal(str(n)).quantize(Decimal("1"), rounding=ROUND_HALF_UP))
def rd(x,y=0):
''' A classical mathematical rounding by Voznica '''
m = int('1'+'0'*y) # multiplier - how many positions to the right
q = x*m # shift to the right by multiplier
c = int(q) # new number
i = int( (q-c)*10 ) # indicator number on the right
if i >= 5:
c += 1
return c/m
Compare:
print( round(0.49), round(0.51), round(0.5), round(1.5), round(2.5), round(0.15,1)) # 0 1 0 2 2 0.1
print( rd(0.49), rd(0.51), rd(0.5), rd(1.5), rd(2.5), rd(0.15,1)) # 0 1 1 2 3 0.2