Mục tiêu của làm tròn là tạo ra ít lỗi nhất. Khi bạn làm tròn một giá trị, quá trình đó đơn giản và dễ hiểu và hầu hết mọi người đều hiểu nó một cách dễ dàng. Khi bạn làm tròn nhiều số cùng một lúc, quy trình sẽ phức tạp hơn - bạn phải xác định cách các lỗi sẽ kết hợp, tức là những gì phải được giảm thiểu.
Các câu trả lời tốt bình chọn bởi Varun Vohra giảm thiểu tổng các lỗi tuyệt đối, và nó rất đơn giản để thực hiện. Tuy nhiên, có những trường hợp cạnh không xử lý - kết quả của làm tròn là 24.25, 23.25, 27.25, 25.25
gì? Một trong những thứ đó cần được làm tròn lên thay vì xuống. Bạn có thể chỉ cần tùy ý chọn cái đầu tiên hoặc cuối cùng trong danh sách.
Có lẽ tốt hơn là sử dụng lỗi tương đối thay vì lỗi tuyệt đối . Làm tròn 23,25 lên đến 24 thay đổi 3,2% trong khi làm tròn 27,25 lên đến 28 chỉ thay đổi 2,8%. Bây giờ có một người chiến thắng rõ ràng.
Có thể điều chỉnh điều này hơn nữa. Một kỹ thuật phổ biến là hình vuông mỗi lỗi, do đó lỗi lớn đếm không tương xứng hơn những cái nhỏ. Tôi cũng sử dụng một ước số phi tuyến tính để nhận được lỗi tương đối - có vẻ không đúng khi lỗi ở mức 1% quan trọng hơn 99 lần so với lỗi ở mức 99%. Trong đoạn mã dưới đây tôi đã sử dụng căn bậc hai.
Thuật toán hoàn chỉnh như sau:
- Tính tổng tỷ lệ phần trăm sau khi làm tròn tất cả xuống và trừ đi 100. Điều này cho bạn biết có bao nhiêu phần trăm phải được làm tròn thay thế.
- Tạo hai điểm lỗi cho mỗi tỷ lệ phần trăm, một khi được làm tròn xuống và một khi được làm tròn lên. Lấy sự khác biệt giữa hai.
- Sắp xếp các khác biệt lỗi được tạo ra ở trên.
- Đối với số phần trăm cần làm tròn lên, hãy lấy một mục từ danh sách đã sắp xếp và tăng tỷ lệ phần trăm làm tròn xuống 1.
Ví dụ, bạn vẫn có thể có nhiều kết hợp có cùng một tổng lỗi 33.3333333, 33.3333333, 33.3333333
. Điều này là không thể tránh khỏi, và kết quả sẽ hoàn toàn tùy ý. Mã tôi đưa ra dưới đây thích làm tròn các giá trị bên trái.
Đặt tất cả lại với nhau trong Python trông như thế này.
def error_gen(actual, rounded):
divisor = sqrt(1.0 if actual < 1.0 else actual)
return abs(rounded - actual) ** 2 / divisor
def round_to_100(percents):
if not isclose(sum(percents), 100):
raise ValueError
n = len(percents)
rounded = [int(x) for x in percents]
up_count = 100 - sum(rounded)
errors = [(error_gen(percents[i], rounded[i] + 1) - error_gen(percents[i], rounded[i]), i) for i in range(n)]
rank = sorted(errors)
for i in range(up_count):
rounded[rank[i][1]] += 1
return rounded
>>> round_to_100([13.626332, 47.989636, 9.596008, 28.788024])
[14, 48, 9, 29]
>>> round_to_100([33.3333333, 33.3333333, 33.3333333])
[34, 33, 33]
>>> round_to_100([24.25, 23.25, 27.25, 25.25])
[24, 23, 28, 25]
>>> round_to_100([1.25, 2.25, 3.25, 4.25, 89.0])
[1, 2, 3, 4, 90]
Như bạn có thể thấy với ví dụ cuối cùng, thuật toán này vẫn có khả năng mang lại kết quả không trực quan. Mặc dù 89.0 không cần làm tròn bất cứ điều gì, một trong những giá trị trong danh sách đó cần được làm tròn; lỗi tương đối thấp nhất dẫn đến việc làm tròn giá trị lớn đó thay vì các lựa chọn thay thế nhỏ hơn nhiều.
Câu trả lời này ban đầu ủng hộ việc đi qua mọi sự kết hợp có thể của làm tròn lên / làm tròn xuống, nhưng như đã chỉ ra trong các ý kiến, một phương pháp đơn giản hơn sẽ hiệu quả hơn. Thuật toán và mã phản ánh sự đơn giản hóa đó.