在 Python 中使用“round”和“Decimal.quantize”舍入小數和整數

商業

下面解釋如何在 Python 中通過舍入或舍入到偶數來對數字進行舍入。這些數字被假定為浮點浮點或整數 int 類型。

  • 內置函數(例如在編程語言中):round()
    • 將小數四捨五入到任意位數。
    • 將整數四捨五入為任意位數。
    • round() 舍入到偶數,而不是普通舍入
  • 標準庫decimalquantize()
    • Decimal創建對象
    • 將小數四捨五入為任意位數並四捨五入為偶數
    • 將整數四捨五入為任意位數並四捨五入為偶數
  • 定義一個新函數
    • 將小數四捨五入到任意位數。
    • 將整數四捨五入為任意位數
    • 注意:對於負值

注意,如上所述,內置函數 round 不是一般的捨入,而是捨入到偶數。詳情見下文。

內置函數(例如在編程語言中):round()

Round() 作為內置函數提供。它可以在不導入任何模塊的情況下使用。

第一個參數是原始數字,第二個參數是位數(要四捨五入的位數)。

將小數四捨五入到任意位數。

以下是浮點浮點類型的處理示例。

如果省略第二個參數,則將其四捨五入為整數。該類型也變為整數 int 類型。

f = 123.456

print(round(f))
# 123

print(type(round(f)))
# <class 'int'>

如果指定了第二個參數,則返回浮點浮點類型。

如果指定正整數,則指定小數位;如果指定了負整數,則指定整數位。 -1 舍入到最接近的十分之一,-2 舍入到最接近的百分之一,0 舍入到整數(第一位),但返回浮點類型,與省略時不同。

print(round(f, 1))
# 123.5

print(round(f, 2))
# 123.46

print(round(f, -1))
# 120.0

print(round(f, -2))
# 100.0

print(round(f, 0))
# 123.0

print(type(round(f, 0)))
# <class 'float'>

將整數四捨五入為任意位數。

以下是整型 int 類型的處理示例。

如果省略第二個參數,或者指定 0 或正整數,則按原樣返回原始值。如果指定了負整數,則將其四捨五入到相應的整數位。在這兩種情況下,都會返回整數 int 類型。

i = 99518

print(round(i))
# 99518

print(round(i, 2))
# 99518

print(round(i, -1))
# 99520

print(round(i, -2))
# 99500

print(round(i, -3))
# 100000

round() 舍入到偶數,而不是普通舍入

請注意,使用 Python 3 中的內置 round() 函數舍入到偶數,而不是一般舍入。

官方文檔中寫的,0.5四捨五入為0,5四捨五入為0,以此類推。

print('0.4 =>', round(0.4))
print('0.5 =>', round(0.5))
print('0.6 =>', round(0.6))
# 0.4 => 0
# 0.5 => 0
# 0.6 => 1

print('4 =>', round(4, -1))
print('5 =>', round(5, -1))
print('6 =>', round(6, -1))
# 4 => 0
# 5 => 0
# 6 => 10

四捨五入到偶數的定義如下。

如果分數小於 0.5,則向下取整;如果分數大於 0.5,則向上取整;如果分數恰好為 0.5,則將其向上舍入到向下舍入和向上舍入之間的偶數。
Rounding – Wikipedia

0.5 並不總是被截斷。

print('0.5 =>', round(0.5))
print('1.5 =>', round(1.5))
print('2.5 =>', round(2.5))
print('3.5 =>', round(3.5))
print('4.5 =>', round(4.5))
# 0.5 => 0
# 1.5 => 2
# 2.5 => 2
# 3.5 => 4
# 4.5 => 4

在某些情況下,舍入到偶數的定義甚至不適用於小數點後兩位的處理。

print('0.05 =>', round(0.05, 1))
print('0.15 =>', round(0.15, 1))
print('0.25 =>', round(0.25, 1))
print('0.35 =>', round(0.35, 1))
print('0.45 =>', round(0.45, 1))
# 0.05 => 0.1
# 0.15 => 0.1
# 0.25 => 0.2
# 0.35 => 0.3
# 0.45 => 0.5

這是因為小數不能完全表示為浮點數,如官方文檔中所述。

浮點數的 round() 行為可能會讓您感到驚訝:例如,round(2.675, 2) 會給你 2.67 而不是預期的 2.68。這不是錯誤。:這是因為大多數小數不能用浮點數精確表示。
round() — Built-in Functions — Python 3.10.2 Documentation

如果要實現小數的一般舍入或精確舍入為偶數,可以使用標準庫十進制量化(如下所述)或定義一個新函數。

另請注意,Python 2 中的 round() 不是四捨五入到偶數,而是四捨五入。

標準庫十進制的 quantize()

標準庫的十進制模塊可用於處理精確的十進制浮點數。

使用 decimal 模塊的 quantize() 方法,可以通過指定舍入模式對數字進行舍入。

quantize() 方法的參數舍入的設置值分別具有以下含義。

  • ROUND_HALF_UP:一般舍入
  • ROUND_HALF_EVEN:四捨五入為偶數

十進制模塊是一個標準庫,所以不需要額外安裝,但需要導入。

from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN

創建十進制對象

Decimal() 可用於創建 Decimal 類型的對象。

如果將浮點類型指定為參數,則可以看到該值實際被視為什麼。

print(Decimal(0.05))
# 0.05000000000000000277555756156289135105907917022705078125

print(type(Decimal(0.05)))
# <class 'decimal.Decimal'>

如示例中所示,0.05 不完全被視為 0.05。這就是為什麼上述內置函數 round() 舍入到與示例中的十進制值(包括 0.05)不同的值的原因。

由於 0.5 是二分之一(2 的 -1 次方),因此可以精確地用二進製表示法表示。

print(Decimal(0.5))
# 0.5

如果指定字符串類型 str 而不是 float 類型,它將被視為精確值的 Decimal 類型。

print(Decimal('0.05'))
# 0.05

將小數四捨五入為任意位數並四捨五入為偶數

從 Decimal 類型的對象調用 quantize() 以四捨五入該值。

quantize() 的第一個參數是一個字符串,其位數與您要查找的位數相同,例如“0.1”或“0.01”。

此外,參數 ROUNDING 指定舍入模式;如果指定了 ROUND_HALF_UP,則使用一般舍入。

f = 123.456

print(Decimal(str(f)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
# 123

print(Decimal(str(f)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP))
# 123.5

print(Decimal(str(f)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
# 123.46

與內置函數 round() 不同,0.5 舍入為 1。

print('0.4 =>', Decimal(str(0.4)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
print('0.5 =>', Decimal(str(0.5)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
print('0.6 =>', Decimal(str(0.6)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
# 0.4 => 0
# 0.5 => 1
# 0.6 => 1

如果參數舍入設置為 ROUND_HALF_EVEN,則舍入到偶數,如內置函數 round()。

如前所述,如果指定浮點浮點類型作為 Decimal() 的參數,則將其視為 Decimal 對象,其值等於浮點類型的實際值,因此使用 quantize() 的結果方法將與預期不同,就像內置函數 round() 一樣。

print('0.05 =>', round(0.05, 1))
print('0.15 =>', round(0.15, 1))
print('0.25 =>', round(0.25, 1))
print('0.35 =>', round(0.35, 1))
print('0.45 =>', round(0.45, 1))
# 0.05 => 0.1
# 0.15 => 0.1
# 0.25 => 0.2
# 0.35 => 0.3
# 0.45 => 0.5

print('0.05 =>', Decimal(0.05).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.15 =>', Decimal(0.15).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.25 =>', Decimal(0.25).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.35 =>', Decimal(0.35).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.45 =>', Decimal(0.45).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
# 0.05 => 0.1
# 0.15 => 0.1
# 0.25 => 0.2
# 0.35 => 0.3
# 0.45 => 0.5

如果 Decimal() 的參數指定為 str 類型的字符串,則將其視為恰好具有該值的 Decimal 對象,因此結果符合預期。

print('0.05 =>', Decimal(str(0.05)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.15 =>', Decimal(str(0.15)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.25 =>', Decimal(str(0.25)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.35 =>', Decimal(str(0.35)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.45 =>', Decimal(str(0.45)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
# 0.05 => 0.0
# 0.15 => 0.2
# 0.25 => 0.2
# 0.35 => 0.4
# 0.45 => 0.4

由於 0.5 可以通過 float 類型正確處理,因​​此在舍入為整數時指定 float 類型作為 Decimal() 的參數是沒有問題的,但在舍入到小數位時指定字符串 str 類型更安全。

例如,2.675 實際上是 2.67499…. 浮點型。因此,如果要四捨五入到兩位小數,必須在 Decimal() 中指定一個字符串,否則無論是四捨五入到最接近的整數 (ROUND_HALF_UP) 還是偶數 (ROUND_HALF_EVEN),結果都會與預期結果不同)。

print(Decimal(2.675))
# 2.67499999999999982236431605997495353221893310546875

print(Decimal(2.675).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
# 2.67

print(Decimal(str(2.675)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
# 2.68

print(Decimal(2.675).quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN))
# 2.67

print(Decimal(str(2.675)).quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN))
# 2.68

注意quantize()方法返回的是Decimal類型的數字,所以如果要對float類型的數字進行操作,需要使用float()將其轉換為float類型,否則會報錯。

d = Decimal('123.456').quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

print(d)
# 123.46

print(type(d))
# <class 'decimal.Decimal'>

# print(1.2 + d)
# TypeError: unsupported operand type(s) for +: 'float' and 'decimal.Decimal'

print(1.2 + float(d))
# 124.66

將整數四捨五入為任意位數並四捨五入為偶數

如果你想四捨五入到一個整數,指定像“10”這樣的第一個參數不會給你想要的結果。

i = 99518

print(Decimal(i).quantize(Decimal('10'), rounding=ROUND_HALF_UP))
# 99518

這是因為 quantize() 根據 Decimal 對象的指數進行舍入,但 Decimal(’10’) 的指數是 0,而不是 1。

您可以通過使用 E 作為指數字符串來指定任意指數(例如,’1E1’)。可以在 as_tuple 方法中檢查指數指數。

print(Decimal('10').as_tuple())
# DecimalTuple(sign=0, digits=(1, 0), exponent=0)

print(Decimal('1E1').as_tuple())
# DecimalTuple(sign=0, digits=(1,), exponent=1)

實際上,結果將使用 E 以指數表示法。如果您想使用普通表示法,或者如果您想在舍入後使用整數 int 類型進行操作,請使用 int() 轉換結果。

print(Decimal(i).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP))
# 9.952E+4

print(int(Decimal(i).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
# 99520

print(int(Decimal(i).quantize(Decimal('1E2'), rounding=ROUND_HALF_UP)))
# 99500

print(int(Decimal(i).quantize(Decimal('1E3'), rounding=ROUND_HALF_UP)))
# 100000

如果參數舍入設置為 ROUND_HALF_UP,將進行一般舍入,例如,5 將舍入為 10。

print('4 =>', int(Decimal(4).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
print('5 =>', int(Decimal(5).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
print('6 =>', int(Decimal(6).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
# 4 => 0
# 5 => 10
# 6 => 10

當然,如果指定為字符串也沒有問題。

定義一個新函數

使用decimal模塊的方法準確且安全,但如果您不習慣類型轉換,您可以定義一個新函數來實現一般舍入。

有很多可能的方法可以做到這一點,例如,下面的函數。

def my_round(val, digit=0):
    p = 10 ** digit
    return (val * p * 2 + 1) // 2 / p

如果您不需要指定位數並始終四捨五入到小數點後一位,則可以使用更簡單的形式。

my_round_int = lambda x: int((x * 2 + 1) // 2)

如果您需要精確,使用小數會更安全。

以下內容僅供參考。

將小數四捨五入到任意位數。

print(int(my_round(f)))
# 123

print(my_round_int(f))
# 123

print(my_round(f, 1))
# 123.5

print(my_round(f, 2))
# 123.46

與舍入不同,0.5 按照一般舍入變為 1。

print(int(my_round(0.4)))
print(int(my_round(0.5)))
print(int(my_round(0.6)))
# 0
# 1
# 1

將整數四捨五入為任意位數

i = 99518

print(int(my_round(i, -1)))
# 99520

print(int(my_round(i, -2)))
# 99500

print(int(my_round(i, -3)))
# 100000

與舍入不同,根據普通舍入,5 變為 10。

print(int(my_round(4, -1)))
print(int(my_round(5, -1)))
print(int(my_round(6, -1)))
# 0
# 10
# 10

注意:對於負值

在上面的示例函數中,-0.5 舍入為 0。

print(int(my_round(-0.4)))
print(int(my_round(-0.5)))
print(int(my_round(-0.6)))
# 0
# 0
# -1

對負值進行四捨五入的想法有很多種,但是如果要將-0.5變成-1,可以修改如下,例如

import math

def my_round2(val, digit=0):
    p = 10 ** digit
    s = math.copysign(1, val)
    return (s * val * p * 2 + 1) // 2 / p * s

print(int(my_round2(-0.4)))
print(int(my_round2(-0.5)))
print(int(my_round2(-0.6)))
# 0
# -1
# -1