web-dev-qa-db-ja.com

Pythonで浮動小数点数の半分を適切に切り上げる方法は?

私は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 を使用しようとしましたが、結果は同じです。

42
Delgan

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
37
Martijn Pieters

例えば:

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

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)

数値を適切に切り上げまたは切り捨てます。

12
fedor2612

表示されている動作は、IEEE 754の一般的な丸め動作です。入力と等しく異なる2つの数値から選択する必要がある場合、常に偶数を選択します。この動作の利点は、平均の丸め効果がゼロであるということです。同じ数の数値が切り上げおよび切り捨てられます。一貫した方向に半分の数を丸めると、丸めは期待値に影響します。

目的が公平な丸めである場合、表示されている動作は正しいですが、それが常に必要なわけではありません。

必要な丸めの種類を取得するための1つのトリックは、0.5を追加してからフロアを取得することです。たとえば、0.5を2.5に追加すると、3が得られ、フロアが3になります。

5

短いバージョン: 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はすべてオプションです。

3
rmunn

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

1
Joe Cat

数値の分野では、最も近い偶数への丸めが一般的になっています。 「端数処理」は、より大きな結果へのわずかな偏りを生み出します。

したがって、科学的確立の観点から見ると、roundの動作は正しいです。

1
MRocklin

別の解決策があります。 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

0
discover

次の解決策は、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
0
Yuchen Peng

質問では、これは基本的に正の整数を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))
0
Gursel Karacor

次を使用できます。

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

ライブラリなしの古典的な数学的丸め

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
0
Coworker