簡體   English   中英

Python 3 使用 ROUND_HALF_UP 上下文的十進制四舍五入

[英]Python 3 Decimal rounding half down with ROUND_HALF_UP context

任何人都可以解釋或提出修復為什么當我在 Python 3 中四舍五入小數並將上下文設置為四舍五入時,它將 2.5 舍入為 2,而在 Python 2 中它正確地四舍五入為 3:

Python 3.4.3 和 3.5.2:

>>> import decimal
>>> context = decimal.getcontext()
>>> context.rounding = decimal.ROUND_HALF_UP
>>> round(decimal.Decimal('2.5'))
2
>>> decimal.Decimal('2.5').__round__()
2
>>> decimal.Decimal('2.5').quantize(decimal.Decimal('1'), rounding=decimal.ROUND_HALF_UP)
Decimal('3')

Python 2.7.6:

>>> import decimal
>>> context = decimal.getcontext()
>>> context.rounding = decimal.ROUND_HALF_UP
>>> round(decimal.Decimal('2.5'))
3.0
>>> decimal.Decimal('2.5').quantize(decimal.Decimal('1'), rounding=decimal.ROUND_HALF_UP)
Decimal('3')

請注意,當您調用round您將獲得一個浮點值,而不是Decimal round是將值強制轉換為float,然后根據舍入浮點數的規則舍入該值。

如果在調用round()時使用可選的ndigits參數,則會返回Decimal結果,在這種情況下,它將按預期方式舍入。

Python 3.4.1 (default, Sep 24 2015, 20:41:10) 
[GCC 4.9.2 20150212 (Red Hat 4.9.2-6)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import decimal
>>> context = decimal.getcontext()
>>> context.rounding = decimal.ROUND_HALF_UP
>>> round(decimal.Decimal('2.5'), 0)
Decimal('3')

我還沒有找到round(someDecimal)文件round(someDecimal)返回一個int而round(someDecimal, ndigits)返回一個小數,但這似乎是Python 3.3及更高版本中發生的情況。 在Python 2.7中,當你調用round()時,你總是得到一個浮點數,但Python 3.3改進了Decimal與Python內置函數的集成。

正如評論中所指出的, round()委托給Decimal.__round__() ,這確實表現出相同的行為:

>>> Decimal('2.5').__round__()
2
>>> Decimal('2.5').__round__(0)
Decimal('3')

我注意到Fraction的文檔說:

__round__()
__round__(ndigits)
The first version returns the nearest int to self, rounding half to even.
The second version rounds self to the nearest multiple of Fraction(1, 10**ndigits)
(logically, if ndigits is negative), again rounding half toward even. 
This method can also be accessed through the round() function.

因此行為是一致的,因為沒有參數它會改變結果的類型並將一半__round__到偶數,但是似乎Decimal無法記錄其__round__方法的行為。

編輯注意Barry Hurley在評論中說, round()被記錄為如果在沒有可選參數的情況下調用則返回int ,如果給出可選參數則返回“浮點值”。 https://docs.python.org/3/library/functions.html#round

擴展@Duncan的答案, round內置函數在python 2和python 3之間改變,以舍入到最接近的偶數(這是統計中的標准)。

Python2文檔:

...如果兩個倍數相等,則舍入遠離0(因此,例如,round(0.5)為1.0,round(-0.5)為-1.0)。

Python3文檔:

...如果兩個倍數相等,則向均勻選擇進行舍入(例如,圓形(0.5)和圓形(-0.5)均為0,圓形(1.5)為2)

如果沒有為ndigits給出參數, round轉換為float (歸功於ndigits的答案), round行為方式與float s相同。

示例(在python3中):

>>> round(2.5)
2
>>> round(2.500000001)
3
>>> round(3.5)
4

這是舍入模式之間的變化的組合round在Python 2比3和重新實施的Decimal從Python來C (參見在“其它最終大規模變化” 功能為3.3部分PEP 398 )。

對於round ,舍入策略已經改變,可以在Python 3.0中的新功能中看到[另請參閱Python 3.x舍入行為 ]。 另外,Python 3 round首先嘗試找到為傳遞的對象定義的適當的__round__方法:

>>> round('1')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: type str doesn't define __round__ method

在Python 2.x它首先嘗試將它專門強制轉換為float ,然后將其舍入:

>>> round('1')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: a float is required

對於Decimal ,在Python 2 ,實現甚至沒有調用__round__方法:

>>> Decimal.__round__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'Decimal' has no attribute '__round__'

因此,呼吁round一個關於Decimal對象裹挾到一個float這得到了使用四舍五入_Py_double_round ; 這導致float總是返回, ndigits是否提供了ndigits的值。 decimal是用純Python實現2.x和(是?)適用於Python 3直到3.2

在Python 3.3當它在C重新實現時, 它有了新的__round__方法

>>> Decimal.__round__
<method '__round__' of 'decimal.Decimal' objects>

而現在,它會在round(<Decimal_object>)被調用時被round round(<Decimal_object>)拾取。

此,映射到PyDec_RoundC ,現在返回PyLong使用默認上下文((一個整數) ROUND_HALF_EVEN )如果參數ndigits不供給,如果它是,來電quantize它,並返回一個新的圓形Decimal對象。

德國和瑞士法律要求您將增值稅從 0.005 舍入到 0.01 (“kaufmännisches Runden”) 我玩了Decimal ,結果是這樣的:

from decimal import *

class Number(object):
    '''generic class for rounding:
        Banking:     ROUND_HALF_EVEN  # default
        Germany:     ROUND_HALF_UP    # for VAT
        Switzerland: ROUND_DOWN       # for some taxes
                     ROUND_HALF_UP    # for some taxes like VAT
    '''
    def __init__(self, digits=2, rounding=ROUND_HALF_UP):
        getcontext().rounding=rounding        
        self.base = Decimal(10) ** -digits
        
    def round(self, value, digits=None):
        if digits:
            base = Decimal(10) ** - digits
        else:
            base = self.base
        return float(Decimal(str(value)).quantize(base))  

例子:

>>> a = Number()
>>> a.round(1301.685)
1301.69

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM