Làm tròn số thập phân và số nguyên trong Python với “round” và “Decimal.quantize

Kinh doanh

Phần sau giải thích cách làm tròn số trong Python bằng cách làm tròn số hoặc làm tròn số thành số chẵn. Các số được giả định là kiểu dấu phẩy động float hoặc kiểu int nguyên.

  • chức năng tích hợp sẵn (ví dụ: trong ngôn ngữ lập trình):round()
    • Làm tròn số thập phân đến bất kỳ số chữ số nào.
    • Làm tròn số nguyên đến bất kỳ số chữ số nào.
    • round () làm tròn đến số chẵn, không làm tròn số chung
  • thư viện tiêu chuẩndecimalquantize()
    • DecimalTạo một đối tượng
    • Làm tròn số thập phân đến bất kỳ số chữ số nào và làm tròn đến số chẵn
    • Làm tròn số nguyên đến bất kỳ số chữ số nào và làm tròn thành số chẵn
  • Xác định một chức năng mới
    • Làm tròn số thập phân đến bất kỳ số chữ số nào.
    • Làm tròn số nguyên đến bất kỳ số chữ số nào
    • Lưu ý: Đối với các giá trị âm

Lưu ý rằng, như đã đề cập ở trên, vòng hàm tích hợp không phải là một phép làm tròn tổng quát mà là một phép làm tròn đến một số chẵn. Xem chi tiết bên dưới.

chức năng tích hợp sẵn (ví dụ: trong ngôn ngữ lập trình):round()

Round () được cung cấp dưới dạng một hàm dựng sẵn. Nó có thể được sử dụng mà không cần nhập bất kỳ mô-đun nào.

Đối số đầu tiên là số ban đầu và đối số thứ hai là số chữ số (làm tròn bao nhiêu chữ số).

Làm tròn số thập phân đến bất kỳ số chữ số nào.

Sau đây là một ví dụ về xử lý đối với kiểu dấu phẩy động float.

Nếu đối số thứ hai bị bỏ qua, đối số này được làm tròn thành số nguyên. Kiểu cũng trở thành kiểu int số nguyên.

f = 123.456

print(round(f))
# 123

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

Nếu đối số thứ hai được chỉ định, nó sẽ trả về kiểu dấu phẩy động.

Nếu một số nguyên dương được chỉ định, thì vị trí thập phân được chỉ định; nếu một số nguyên âm được chỉ định, vị trí số nguyên được chỉ định. -1 làm tròn đến phần mười gần nhất, -2 làm tròn đến hàng trăm gần nhất và 0 làm tròn đến một số nguyên (vị trí đầu tiên), nhưng trả về kiểu float, không giống như khi bị bỏ qua.

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'>

Làm tròn số nguyên đến bất kỳ số chữ số nào.

Sau đây là một ví dụ về xử lý cho kiểu int số nguyên.

Nếu đối số thứ hai bị bỏ qua hoặc nếu 0 hoặc một số nguyên dương được chỉ định, giá trị ban đầu được trả về như hiện tại. Nếu một số nguyên âm được chỉ định, nó được làm tròn thành chữ số nguyên tương ứng. Trong cả hai trường hợp, một kiểu int số nguyên được trả về.

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 () làm tròn đến số chẵn, không làm tròn số chung

Lưu ý rằng làm tròn với hàm round () tích hợp sẵn trong Python 3 làm tròn thành số chẵn, không làm tròn chung.

Như đã viết trong tài liệu chính thức, 0,5 được làm tròn thành 0, 5 được làm tròn thành 0, v.v.

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

Định nghĩa của làm tròn đến một số chẵn như sau.

Nếu phân số nhỏ hơn 0,5, làm tròn nó xuống; nếu phân số lớn hơn 0,5 thì làm tròn số; nếu phân số chính xác là 0,5 thì làm tròn đến số chẵn giữa làm tròn xuống và làm tròn lên.
Rounding – Wikipedia

0,5 không phải lúc nào cũng bị cắt ngắn.

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

Trong một số trường hợp, định nghĩa làm tròn thành số chẵn thậm chí không áp dụng cho xử lý sau hai chữ số thập phân.

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

Điều này là do thực tế là số thập phân không thể được biểu diễn chính xác dưới dạng số dấu phẩy động, như đã nêu trong tài liệu chính thức.

Hành vi của round () đối với các số dấu phẩy động có thể khiến bạn ngạc nhiên:Ví dụ: vòng (2,675, 2) sẽ cho bạn 2,67 thay vì 2,68 như mong đợi. Đây không phải là một lỗi.:Đây là kết quả của thực tế là hầu hết các số thập phân không thể được biểu diễn chính xác bằng số dấu phẩy động.
round() — Built-in Functions — Python 3.10.2 Documentation

Nếu bạn muốn làm tròn tổng quát hoặc làm tròn chính xác các số thập phân thành các số chẵn, bạn có thể sử dụng lượng tử hóa thập phân của thư viện tiêu chuẩn (được mô tả bên dưới) hoặc xác định một hàm mới.

Cũng lưu ý rằng round () trong Python 2 không làm tròn thành số chẵn mà là làm tròn.

quantize () của thư viện tiêu chuẩn thập phân

Mô-đun thập phân của thư viện tiêu chuẩn có thể được sử dụng để xử lý các số dấu phẩy động thập phân chính xác.

Sử dụng phương thức quantize () của mô-đun thập phân, có thể làm tròn số bằng cách chỉ định chế độ làm tròn.

Các giá trị đặt cho đối số làm tròn của phương thức quantize () có ý nghĩa tương ứng như sau.

  • ROUND_HALF_UP:Làm tròn chung
  • ROUND_HALF_EVEN:Làm tròn đến số chẵn

Mô-đun thập phân là một thư viện tiêu chuẩn, vì vậy không cần cài đặt thêm nhưng cần nhập.

from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN

Tạo đối tượng thập phân

Decimal () có thể được sử dụng để tạo các đối tượng kiểu Decimal.

Nếu bạn chỉ định kiểu float làm đối số, bạn có thể thấy giá trị thực sự được coi là gì.

print(Decimal(0.05))
# 0.05000000000000000277555756156289135105907917022705078125

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

Như thể hiện trong ví dụ, 0,05 không được coi là chính xác 0,05. Đây là lý do tại sao hàm tích hợp round () được mô tả ở trên làm tròn thành một giá trị khác với giá trị mong đợi đối với các giá trị thập phân bao gồm 0,05 trong ví dụ.

Vì 0,5 là một nửa (-1 lũy thừa của 2), nó có thể được biểu thị chính xác bằng ký hiệu nhị phân.

print(Decimal(0.5))
# 0.5

Nếu bạn chỉ định kiểu chuỗi str thay vì kiểu float, nó sẽ được coi là kiểu Thập phân của giá trị chính xác.

print(Decimal('0.05'))
# 0.05

Làm tròn số thập phân đến bất kỳ số chữ số nào và làm tròn đến số chẵn

Gọi quantize () từ một đối tượng kiểu Decimal để làm tròn giá trị.

Đối số đầu tiên của quantize () là một chuỗi có cùng số chữ số với số chữ số bạn muốn tìm, chẳng hạn như ‘0,1’ hoặc ‘0,01’.

Ngoài ra, đối số ROUNDING chỉ định chế độ làm tròn; nếu ROUND_HALF_UP được chỉ định, làm tròn chung sẽ được sử dụng.

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

Không giống như hàm tích hợp round (), 0,5 được làm tròn thành 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

Nếu làm tròn đối số được đặt thành ROUND_HALF_EVEN, thì việc làm tròn được thực hiện thành số chẵn như trong hàm tích hợp round ().

Như đã đề cập ở trên, nếu một kiểu float dấu phẩy động được chỉ định làm đối số của Decimal (), thì nó được coi là đối tượng Decimal có giá trị bằng giá trị thực của kiểu float, do đó, kết quả của việc sử dụng quantize () phương thức sẽ khác với những gì được mong đợi, giống như hàm tích hợp 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

Nếu đối số của Decimal () được chỉ định là một chuỗi kiểu str, nó được coi như một đối tượng Decimal có chính xác giá trị đó, vì vậy kết quả là như mong đợi.

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

Vì 0,5 có thể được xử lý chính xác bởi kiểu float, nên không có vấn đề gì khi chỉ định kiểu float làm đối số của Decimal () khi làm tròn thành số nguyên, nhưng sẽ an toàn hơn khi chỉ định kiểu chuỗi str khi làm tròn đến chữ số thập phân.

Ví dụ, 2.675 thực sự là 2.67499 …. trong kiểu float. Do đó, nếu bạn muốn làm tròn đến hai chữ số thập phân, bạn phải chỉ định một chuỗi thành Decimal (), nếu không kết quả sẽ khác với kết quả mong đợi cho dù bạn làm tròn đến số nguyên gần nhất (ROUND_HALF_UP) hay thành số chẵn (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

Lưu ý rằng phương thức quantize () trả về một số kiểu Thập phân, vì vậy nếu bạn muốn thao tác trên một số kiểu float, bạn cần chuyển nó sang kiểu float bằng cách sử dụng float (), nếu không sẽ xảy ra lỗi.

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

Làm tròn số nguyên đến bất kỳ số chữ số nào và làm tròn thành số chẵn

Nếu bạn muốn làm tròn thành một chữ số nguyên, việc chỉ định một cái gì đó như ’10’ làm đối số đầu tiên sẽ không cung cấp cho bạn kết quả mong muốn.

i = 99518

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

Điều này là do quantize () thực hiện làm tròn theo số mũ của đối tượng Decimal, nhưng số mũ của đối tượng Decimal (’10 ‘) là 0, không phải 1.

Bạn có thể chỉ định một số mũ tùy ý bằng cách sử dụng E làm chuỗi số mũ (ví dụ: ‘1E1’). Số mũ lũy thừa có thể được kiểm tra trong phương thức 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)

Như vậy, kết quả sẽ ở dạng ký hiệu mũ bằng cách sử dụng E. Nếu bạn muốn sử dụng ký hiệu bình thường hoặc nếu bạn muốn hoạt động với kiểu int nguyên sau khi làm tròn, hãy sử dụng int () để chuyển đổi kết quả.

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

Nếu làm tròn đối số được đặt thành ROUND_HALF_UP, thì việc làm tròn chung sẽ xảy ra, ví dụ: 5 sẽ được làm tròn thành 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

Tất nhiên, không có vấn đề gì nếu bạn chỉ định nó dưới dạng chuỗi.

Xác định một chức năng mới

Phương pháp sử dụng mô-đun thập phân là chính xác và an toàn, nhưng nếu bạn không cảm thấy thoải mái với việc chuyển đổi kiểu, bạn có thể xác định một hàm mới để đạt được làm tròn chung.

Có nhiều cách khả thi để thực hiện việc này, ví dụ như hàm sau.

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

Nếu bạn không cần chỉ định số chữ số và luôn làm tròn đến chữ số thập phân đầu tiên, bạn có thể sử dụng một biểu mẫu đơn giản hơn.

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

Nếu bạn cần chính xác, sẽ an toàn hơn khi sử dụng số thập phân.

Sau đây là chỉ để tham khảo.

Làm tròn số thập phân đến bất kỳ số chữ số nào.

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

Không giống như làm tròn, 0,5 trở thành 1 khi làm tròn chung.

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

Làm tròn số nguyên đến bất kỳ số chữ số nào

i = 99518

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

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

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

Không giống như làm tròn, 5 trở thành 10 theo cách làm tròn thông thường.

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

Lưu ý: Đối với các giá trị âm

Trong hàm ví dụ trên, -0,5 được làm tròn thành 0.

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

Có nhiều cách khác nhau để suy nghĩ về việc làm tròn các giá trị âm, nhưng nếu bạn muốn biến -0,5 thành -1, bạn có thể sửa đổi nó như sau, ví dụ:

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