簡體   English   中英

Python 3.x 四舍五入

[英]Python 3.x rounding half up

我知道關於 python 中舍入的問題已經被問過多次,但答案對我沒有幫助。 我正在尋找一種將浮點數四舍五入並返回浮點數的方法。 該方法還應該接受一個參數,該參數定義要舍入到的小數位。 我寫了一個實現這種舍入的方法。 但是,我認為它看起來一點也不優雅。

def round_half_up(number, dec_places):
    s = str(number)

    d = decimal.Decimal(s).quantize(
        decimal.Decimal(10) ** -dec_places,
        rounding=decimal.ROUND_HALF_UP)

    return float(d)

我不喜歡它,我必須將浮點數轉換為字符串(以避免浮點數不准確),然后使用十進制模塊。 你有更好的解決方案嗎?

編輯:正如下面的答案所指出的,我的問題的解決方案並不那么明顯,因為正確的舍入首先需要正確表示數字,而浮點數不是這種情況。 所以我希望下面的代碼

def round_half_up(number, dec_places):

    d = decimal.Decimal(number).quantize(
        decimal.Decimal(10) ** -dec_places,
        rounding=decimal.ROUND_HALF_UP)

    return float(d)

(與上面的代碼不同,只是浮點數直接轉換為十進制數而不是先轉換為字符串)在像這樣使用時返回 2.18: round_half_up(2.175, 2)但它不是因為Decimal(2.175)將返回Decimal('2.17499999999999982236431605997495353221893310546875') ,浮點數由計算機表示的方式。 令人驚訝的是,第一個代碼返回 2.18,因為浮點數首先轉換為字符串。 似乎 str() function 對最初要舍入的數字進行了隱式舍入。 所以有兩個舍入發生。 盡管這是我所期望的結果,但它在技術上是錯誤的。

舍入出人意料地難以正確進行,因為您必須非常小心地處理浮點計算。 如果您正在尋找一個優雅的解決方案(簡短、易於理解),那么您所擁有的就是一個很好的起點。 正確地說,您應該將decimal.Decimal(str(number))替換為從數字本身創建小數,這將為您提供其精確表示的十進制版本:

d = Decimal(number).quantize(...)...

Decimal(str(number))有效地舍入兩次,因為將浮點數格式化為字符串表示會執行其自己的舍入。 這是因為str(float value)不會嘗試打印浮點數的完整十進制表示,它只會打印足夠的數字以確保在將這些確切數字傳遞給float構造函數時獲得相同的浮點數。

如果你想保持正確的舍入,但避免依賴於大而復雜的decimal模塊,你當然可以做到,但你仍然需要一些方法來實現正確舍入所需的精確算術。 例如,您可以使用分數

import fractions, math

def round_half_up(number, dec_places=0):
    sign = math.copysign(1, number)
    number_exact = abs(fractions.Fraction(number))
    shifted = number_exact * 10**dec_places
    shifted_trunc = int(shifted)
    if shifted - shifted_trunc >= fractions.Fraction(1, 2):
        result = (shifted_trunc + 1) / 10**dec_places
    else:
        result = shifted_trunc / 10**dec_places
    return sign * float(result)

assert round_half_up(1.49) == 1
assert round_half_up(1.5) == 2
assert round_half_up(1.51) == 2
assert round_half_up(2.49) == 2
assert round_half_up(2.5) == 3
assert round_half_up(2.51) == 3

請注意,上面代碼中唯一棘手的部分是浮點到分數的精確轉換,並且可以卸載到as_integer_ratio()浮點方法,這是小數和分數在內部所做的。 所以如果你真的想去除對fractions的依賴,你可以將小數運算減少為純 integer 運算; 您以犧牲一些易讀性為代價保持在相同的行數內:

def round_half_up(number, dec_places=0):
    sign = math.copysign(1, number)
    exact = abs(number).as_integer_ratio()
    shifted = (exact[0] * 10**dec_places), exact[1]
    shifted_trunc = shifted[0] // shifted[1]
    difference = (shifted[0] - shifted_trunc * shifted[1]), shifted[1]
    if difference[0] * 2 >= difference[1]:  # difference >= 1/2
        shifted_trunc += 1
    return sign * (shifted_trunc / 10**dec_places)

請注意,測試這些函數會突出在創建浮點數時執行的近似值。 例如, print(round_half_up(2.175, 2))打印2.17 ,因為十進制數2.175不能用二進制精確表示,所以它被一個恰好比十進制 2.175 略小的近似值代替。 function 收到該值,發現它小於對應於小數點 2.175 的實際分數,並決定將其向下舍入。 這不是實現的怪癖; 該行為源自浮點數的屬性,並且也存在於round 32 的內置循環中。

我不喜歡它,我必須將浮點數轉換為字符串(以避免浮點數不准確),然后使用十進制模塊。 你有更好的解決方案嗎?

是的; 如果您需要精確地表示諸如 2.675 之類的數字並將它們四舍五入為 2.68 而不是 2.67,請使用Decimal在整個程序中表示您的數字。

沒有其他辦法。 屏幕上顯示為 2.675 的浮點數不是實數 2.675; 事實上,它比 2.675 略小一點,這就是它被四舍五入為 2.67 的原因:

>>> 2.675 - 2
0.6749999999999998

它僅以字符串形式顯示為'2.675'因為這恰好是最短的字符串,例如float(s) == 2.6749999999999998 請注意,這種較長的表示(有很多 9)也不准確。

但是,您編寫四舍五入 function 時, my_round(2.675, 2)不可能四舍五入到2.68並且my_round(2 + 0.6749999999999998, 2)四舍五入到2.67 因為輸入實際上是相同的浮點數。

因此,如果您的數字 2.675 曾經被轉換為浮點數並再次返回,那么您已經丟失了關於它應該向上還是向下取整的信息。 解決方案不是首先讓它浮動。

在嘗試了很長時間來制作優雅的單行 function 之后,我最終得到了與字典大小相當的東西。

我想說最簡單的方法就是

def round_half_up(inp,dec_places):
    return round(inp+0.0000001,dec_places)

我承認這並非在所有情況下都是准確的,但如果您只想要一個簡單的快速解決方法,它應該可以工作。

暫無
暫無

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

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