Python 3.x làm tròn một nửa


8

Tôi biết rằng các câu hỏi về làm tròn con trăn đã được hỏi nhiều lần rồi, nhưng câu trả lời không giúp tôi. Tôi đang tìm kiếm một phương pháp làm tròn số float lên một nửa và trả về số float. Phương thức cũng nên chấp nhận một tham số xác định vị trí thập phân để làm tròn thành. Tôi đã viết một phương pháp thực hiện kiểu làm tròn này. Tuy nhiên, tôi nghĩ rằng nó trông không thanh lịch chút nào.

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)

Tôi không thích nó, rằng tôi phải chuyển đổi float thành một chuỗi (để tránh sự thiếu chính xác của dấu phẩy động) và sau đó làm việc với mô-đun thập phân . Bạn có giải pháp nào tốt hơn không?

Chỉnh sửa: Như đã chỉ ra trong các câu trả lời dưới đây, giải pháp cho vấn đề của tôi không rõ ràng vì làm tròn chính xác đòi hỏi phải thể hiện chính xác các số ở vị trí đầu tiên và đây không phải là trường hợp nổi. Vì vậy, tôi mong đợi rằng các mã sau đây

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)

(khác với mã trên chỉ bởi thực tế rằng số lượng phao được trực tiếp chuyển đổi thành số thập phân và không để một chuỗi đầu tiên) để trở về 2.18 khi được sử dụng như thế này: round_half_up(2.175, 2)Nhưng nó không vì Decimal(2.175)sẽ trở lại Decimal('2.17499999999999982236431605997495353221893310546875'), cách phao số được đại diện bởi máy tính. Đáng ngạc nhiên, mã đầu tiên trả về 2,18 vì số float được chuyển đổi thành chuỗi đầu tiên. Có vẻ như hàm str () tiến hành làm tròn ẩn cho số ban đầu có nghĩa là được làm tròn. Vì vậy, có hai vòng diễn ra. Mặc dù đây là kết quả mà tôi mong đợi, nhưng nó là sai về mặt kỹ thuật.


@IcesHay Tôi không chắc là tôi có hiểu ý bạn hay không bằng cách làm tròn số lên một nửa. Bạn có thể cho chúng tôi một số ví dụ?
Kenivia

@Kenivia Ý của tôi là, bạn làm tròn số thập phân có liên quan từ 0-4 trở xuống và từ 5-9 trở lên: Nếu làm tròn đến 0 chữ số thập phân: 2,4 = 2; 2,5 = 3; 3,5 = 4, v.v.
IcesHay 11/11/19

Câu trả lời:


7

Làm tròn đáng ngạc nhiên là khó để làm đúng , bởi vì bạn phải xử lý các phép tính dấu phẩy động rất cẩn thận. Nếu bạn đang tìm kiếm một giải pháp thanh lịch (ngắn gọn, dễ hiểu), những gì bạn thích giống như một điểm khởi đầu tốt. Để chính xác, bạn nên thay thế decimal.Decimal(str(number))bằng cách tạo số thập phân từ chính số đó, nó sẽ cung cấp cho bạn phiên bản thập phân của biểu diễn chính xác của nó:

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

Decimal(str(number))làm tròn hiệu quả hai lần , khi định dạng float vào biểu diễn chuỗi thực hiện làm tròn riêng. Điều này là do str(float value)sẽ không cố gắng in đại diện thập phân đầy đủ của số float, nó sẽ chỉ in đủ các chữ số để đảm bảo rằng bạn sẽ nhận được cùng một số float nếu bạn chuyển các chữ số chính xác đó cho hàm floattạo.

Nếu bạn muốn giữ lại làm tròn chính xác, nhưng tránh phụ thuộc vào decimalmô-đun lớn và phức tạp , bạn chắc chắn có thể làm điều đó, nhưng bạn vẫn cần một số cách để thực hiện các phép đo chính xác cần thiết cho làm tròn chính xác. Ví dụ: bạn có thể sử dụng phân số :

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

Lưu ý rằng phần khó khăn duy nhất trong đoạn mã trên là chuyển đổi chính xác điểm nổi thành phân số và có thể được nạp vào as_integer_ratio()phương thức float, đây là điều mà cả số thập phân và phân số thực hiện bên trong. Vì vậy, nếu bạn thực sự muốn loại bỏ sự phụ thuộc vào fractions, bạn có thể giảm số học phân số thành số học số nguyên; bạn ở trong cùng một số dòng với chi phí của một số mức độ dễ đọc:

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)

Lưu ý rằng việc kiểm tra các hàm này mang lại sự nổi bật cho các phép tính gần đúng được thực hiện khi tạo các số có dấu phẩy động. Ví dụ, print(round_half_up(2.175, 2))in 2.17vì số thập phân 2.175không thể được biểu diễn chính xác dưới dạng nhị phân, do đó, nó được thay thế bằng một xấp xỉ xảy ra nhỏ hơn một chút so với số thập phân 2.175. Hàm nhận giá trị đó, tìm thấy nó nhỏ hơn phân số thực tương ứng với số thập phân 2,175 và quyết định làm tròn nó xuống . Đây không phải là một sự châm biếm của việc thực hiện; hành vi xuất phát từ các thuộc tính của số dấu phẩy động và cũng có trong phần rounddựng sẵn của Python 32 .


Vấn đề với d = Decimal(number).quantize(...)là, không có đại diện chính xác của số float. Vì vậy, Decimal(2.175)sẽ cung cấp cho bạn Số thập phân ('2.17499999 ...') (và do đó làm tròn sẽ không chính xác) trong khi Decimal('2.175')là Số thập phân ('2.175'). Đó là lý do tại sao tôi chuyển đổi thành chuỗi đầu tiên.
IcesHay

@IcesHay Đúng là không có đại diện chính xác của một số như 0,1 (tức là phân số 1/10) trong nhị phân vì mẫu số của nó không phải là lũy thừa của 2. Nhưng một khi bạn có một số float thực tế, thì phép tính gần đúng đã xảy ra , và số lượng bạn có trong bộ nhớ không có một đại diện chính xác, nó được cung cấp bởi as_integer_ratio()(trong trường hợp 0.1, đó sẽ là 3602879701896397/36028797018963968). Hàm decimal.Decimal(float)tạo sử dụng biểu diễn đó và làm tròn tiếp theo sẽ chính xác và được thực hiện chính xác.
dùng4815162342

Bạn có chắc chắn về điều đó không? Bởi vì tôi đã kiểm tra chức năng round_half_up(2.175, 2)và nó trả về 2,17 không chính xác. Có lẽ có điều gì khác tôi đang làm sai?
IcesHay

@IcesHay Đó chính xác là điều, 2.175 được tính gần đúng khi được đọc dưới dạng float, thành một số có thể biểu diễn chính xác như phân số 2448832297382707/1125899906842624, nhỏ hơn một chút so với 2.175. (Bạn có thể kiểm tra cái này '%.20f' % 2.175để đánh giá '2.17499999999999982236'.) Ví dụ, trong Python 2.7, sử dụng tính năng làm tròn nửa vòng, làm tròn (2.175) trả về 2.17 và loại kết quả này được ghi nhận là giới hạn của dấu phẩy động.
dùng4815162342

1
@IcesHay Đó không phải là cách điểm nổi hoạt động. Khi một chuỗi được đọc thành dấu phẩy động, số ban đầu (sẽ phân biệt "2.175" với "2.174999999999 ...") sẽ bị mất không thể cứu vãn. Câu hỏi của bạn là về việc thực hiện một nửa số, đó là những gì câu trả lời này cung cấp. Có vẻ như vấn đề thực sự của bạn là một vấn đề hoàn toàn khác, vì bây giờ hóa ra ngay cả Python 2 round(thực hiện nửa vòng) cũng không đủ tốt. Vui lòng chỉnh sửa câu hỏi để chỉ định trường hợp sử dụng thực tế - có lẽ nó sẽ được thỏa mãn bằng cách tránh nổi và sử dụng số thập phân.
dùng4815162342

1

Tôi không thích nó, rằng tôi phải chuyển đổi float thành một chuỗi (để tránh sự thiếu chính xác của dấu phẩy động) và sau đó làm việc với mô-đun thập phân. Bạn có giải pháp nào tốt hơn không?

Đúng; sử dụng Decimalđể thể hiện các số của bạn trong toàn bộ chương trình của bạn, nếu bạn cần thể hiện chính xác các số như 2.675 và làm tròn chúng thành 2,68 thay vì 2,67.

Không có cách nào khác. Số dấu phẩy động được hiển thị trên màn hình của bạn là 2.675 không phải là số thực 2.675; thực tế, nó rất ít hơn 2.675, đó là lý do tại sao nó được làm tròn xuống 2,67:

>>> 2.675 - 2
0.6749999999999998

Nó chỉ hiển thị ở dạng chuỗi '2.675'vì đó là chuỗi ngắn nhất float(s) == 2.6749999999999998. Lưu ý rằng đại diện dài hơn này (với nhiều số 9) cũng không chính xác.

Tuy nhiên, bạn viết chức năng làm tròn của mình, không thể my_round(2.675, 2)làm tròn lên 2.68và cũng có thể my_round(2 + 0.6749999999999998, 2)làm tròn xuống 2.67; bởi vì các đầu vào thực sự là cùng một số dấu phẩy động.

Vì vậy, nếu số 2.675 của bạn từng được chuyển đổi thành số float và quay lại, bạn đã mất thông tin về việc nên làm tròn lên hay xuống. Giải pháp là không làm cho nó nổi ở nơi đầu tiên.


-2

Sau một thời gian rất dài để tạo ra một chức năng một dòng thanh lịch, cuối cùng tôi đã nhận được một cái gì đó có thể so sánh với một từ điển có kích thước.

Tôi muốn nói rằng cách đơn giản nhất để làm điều này chỉ là

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

Tôi sẽ thừa nhận rằng điều này không chính xác trong mọi trường hợp, nhưng sẽ hoạt động nếu bạn chỉ muốn một cách giải quyết nhanh chóng đơn giản.


Cảm ơn bạn đã nỗ lực của bạn. Giải pháp của bạn có thể đơn giản, nhưng đó là một cách dễ dàng, và không thực tế lắm. Chỉ cần làm rõ một lần nữa: Tôi đang tìm kiếm một giải pháp, đó là làm tròn các giá trị float chính xác "một nửa" đến bất kỳ vị trí thập phân nào. Tôi tò mò nếu có ai khác có vấn đề tương tự và có một giải pháp thanh lịch và hiệu quả cho vấn đề này.
IcesHay

2
Giải pháp này không chính xác; ví dụ: round_half_up(1.499999999, 0)trả về 2.0 thay vì 1.0.
dùng4815162342

@ user4815162342 nó không tròn như bình thường từ 1,5 xuống, đó là lý do tại sao op hỏi câu hỏi này ngay từ đầu
Kenivia

@ user4815162342 nhưng có, nó không chính xác trong những trường hợp đó. nhưng nếu bạn chỉ muốn làm tròn 1,5 thì tôi nghĩ đây là cách giải quyết nhanh. Câu trả lời tuyệt vời từ phần của bạn btw :)
Kenivia

1
Cảm ơn. Nếu bạn hỏi tôi ngày hôm qua, tôi đã nói rằng làm tròn 1.499999999 thành 2.0 là không chính xác nói chung và cho OP nói riêng. Nhưng sau vòng thảo luận mới nhất với OP trong các bình luận cho câu trả lời của tôi, tôi không còn chắc chắn nữa, và tôi bắt đầu nghĩ rằng câu hỏi không thể trả lời được ở dạng hiện tại. Trong ánh sáng đó, tôi đang giải cứu downvote của mình.
dùng4815162342
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.