Hành vi làm tròn Python 3.x


176

Tôi vừa đọc lại What New trong Python 3.0 và nó nói:

Chiến lược làm tròn hàm () và kiểu trả về đã thay đổi. Các trường hợp nửa chừng chính xác bây giờ được làm tròn đến kết quả thậm chí gần nhất thay vì từ 0. (Ví dụ: vòng (2.5) bây giờ trả về 2 thay vì 3.)

và tài liệu cho vòng :

Đối với các loại tích hợp hỗ trợ round (), các giá trị được làm tròn đến bội số gần nhất của 10 với công suất trừ n; Nếu hai bội số gần nhau như nhau, làm tròn được thực hiện theo lựa chọn chẵn

Vì vậy, theo v2.7.3 :

In [85]: round(2.5)
Out[85]: 3.0

In [86]: round(3.5)
Out[86]: 4.0

như tôi đã mong đợi Tuy nhiên, bây giờ theo v3.2.3 :

In [32]: round(2.5)
Out[32]: 2

In [33]: round(3.5)
Out[33]: 4

Điều này có vẻ phản trực giác và trái ngược với những gì tôi hiểu về làm tròn số (và bị ràng buộc để làm tăng người lên). Tiếng Anh không phải là ngôn ngữ mẹ đẻ của tôi nhưng cho đến khi tôi đọc nó, tôi nghĩ rằng tôi biết làm tròn có nghĩa là gì: - / Tôi chắc chắn tại thời điểm v3 được giới thiệu, phải có một số cuộc thảo luận về điều này, nhưng tôi không thể tìm thấy lý do chính đáng trong tìm kiếm của tôi.

  1. Có ai có cái nhìn sâu sắc về lý do tại sao điều này đã được thay đổi thành này?
  2. Có bất kỳ ngôn ngữ lập trình chính thống nào khác (ví dụ: C, C ++, Java, Perl, ..) thực hiện kiểu làm tròn này (với tôi không nhất quán) không?

Tôi đang thiếu gì ở đây?

CẬP NHẬT: Nhận xét của @ Li-aungYip lại "Làm tròn ngân hàng" đã cho tôi cụm từ / từ khóa tìm kiếm phù hợp để tìm kiếm và tôi đã tìm thấy câu hỏi SO này: Tại sao .NET sử dụng làm tròn ngân hàng làm mặc định? , vì vậy tôi sẽ đọc nó một cách cẩn thận.


27
Tôi không có thời gian để tìm kiếm điều này, nhưng tôi tin rằng đây được gọi là "Làm tròn ngân hàng". Tôi tin rằng nó phổ biến trong ngành tài chính.
Li-aung Yip

2
@sberry tốt, vâng, hành vi của nó phù hợp với mô tả của chính nó. Vì vậy, nếu nó nói "làm tròn" là nhân đôi giá trị của nó và thực hiện nó, thì nó cũng sẽ nhất quán :) .. nhưng có vẻ trái ngược với những gì làm tròn thường có nghĩa . Vì vậy, tôi đang tìm kiếm một sự hiểu biết tốt hơn.
Levon

1
@ Li-aungYip Cảm ơn bạn đã dẫn đầu "Làm tròn ngân hàng" .. Tôi sẽ tìm kiếm nó.
Levon


3
Chỉ cần lưu ý: Làm tròn ngân hàng không chỉ phổ biến trong tài chính. Đây là cách tôi được dạy để làm tròn ở trường tiểu học đã có từ những năm 70 :-)
Lennart Regebro 11/03/13

Câu trả lời:


160

Cách của Python 3.0 được coi là phương pháp làm tròn tiêu chuẩn ngày nay, mặc dù một số triển khai ngôn ngữ chưa có trên xe buýt.

Kỹ thuật "luôn làm tròn 0,5 lên" đơn giản dẫn đến sai lệch nhỏ so với số cao hơn. Với số lượng lớn các tính toán, điều này có thể là đáng kể. Cách tiếp cận Python 3.0 loại bỏ vấn đề này.

Có nhiều hơn một phương pháp làm tròn được sử dụng phổ biến. IEEE 754, tiêu chuẩn quốc tế cho toán học dấu phẩy động, định nghĩa năm phương pháp làm tròn khác nhau ( phương pháp được sử dụng bởi Python 3.0 là mặc định). Và có những người khác.

Hành vi này không được biết đến rộng rãi như nó phải được. AppleScript là, nếu tôi nhớ chính xác, là người áp dụng sớm phương pháp làm tròn này. Các roundlệnh trong AppleScript thực sự không phục vụ một số tùy chọn, nhưng tròn về phía-thậm chí là mặc định vì nó là trong IEEE 754. Rõ ràng người kỹ sư thực hiện các roundlệnh đã quá chán ngán với tất cả các yêu cầu để "làm cho nó làm việc như tôi đã học được trong trường "mà ông đã thực hiện chỉ là: round 2.5 rounding as taught in schoollà một lệnh AppleScript hợp lệ. :-)


4
Tôi đã không biết về "phương pháp làm tròn tiêu chuẩn mặc định này khá phổ biến hiện nay", bạn (hoặc bất kỳ ai khác) có biết C / C ++ / Java / Perl hoặc bất kỳ ngôn ngữ "luồng chính" nào khác thực hiện làm tròn theo cùng một cách không?
Levon

3
Ruby làm điều đó. Ngôn ngữ .NET của Microsoft làm điều đó. Mặc dù vậy, Java không xuất hiện. Tôi không thể theo dõi nó cho mọi ngôn ngữ có thể, nhưng tôi đoán nó phổ biến nhất trong các ngôn ngữ được thiết kế gần đây. Tôi tưởng tượng C và C ++ đã đủ tuổi để họ không.
loại

5
ruby trả lại 3cho2.5.round
jfs

14
Tôi đã thêm một chút về cách xử lý của AppleScript vì tôi thích cách mỉa mai hành vi "cũ" được thực hiện.
loại

2
@kindall Phương pháp này là chế độ làm tròn mặc định của IEEE kể từ năm 1985 (khi IEEE 754-1985 được xuất bản). Tuy nhiên, đây cũng là chế độ làm tròn mặc định trong C vì ít nhất là C89 (và cả trong C ++), tuy nhiên , vì C99 (và C ++ 11 có hỗ trợ lẻ tẻ trước đó), chức năng "round ()" đã có sẵn sử dụng quan hệ vòng từ số 0 thay vào đó. Làm tròn điểm nổi bên trong và nhóm chức năng rint () vẫn tuân theo cài đặt chế độ làm tròn, mặc định cho các mối quan hệ tròn đến chẵn.
Wlerin

41

Bạn có thể điều khiển làm tròn số bạn nhận được trong Py3000 bằng mô-đun thập phân :

>>> decimal.Decimal('3.5').quantize(decimal.Decimal('1'), 
    rounding=decimal.ROUND_HALF_UP)
>>> Decimal('4')

>>> decimal.Decimal('2.5').quantize(decimal.Decimal('1'),    
    rounding=decimal.ROUND_HALF_EVEN)
>>> Decimal('2')

>>> decimal.Decimal('3.5').quantize(decimal.Decimal('1'), 
    rounding=decimal.ROUND_HALF_DOWN)
>>> Decimal('3')

Cảm ơn .. Tôi không quen thuộc với mô-đun này. Bất cứ ý tưởng làm thế nào tôi sẽ có được hành vi của Python v 2.x? Các ví dụ bạn cho thấy dường như không làm điều đó. Chỉ tò mò nếu điều đó sẽ có thể.
Levon

1
@Levon: Hằng số ROUND_HALF_UPgiống như hành vi cũ của Python 2.X.
dawg

2
Bạn cũng có thể đặt bối cảnh cho mô-đun thập phân thực hiện điều này cho bạn hoàn toàn. Xem setcontext()chức năng.
loại

Đây chính xác là những gì tôi đang tìm kiếm ngày hôm nay. Hoạt động như mong đợi trong Python 3.4.3. Cũng đáng chú ý, bạn có thể kiểm soát số tiền làm tròn bằng cách thay đổi quantize(decimal.Decimal('1')thành quantize(decimal.Decimal('0.00')nếu bạn muốn làm tròn đến số 100 gần nhất, chẳng hạn như để kiếm tiền.
Igor

Giải pháp này hoạt động như một sự thay thế round(number, ndigits)miễn ndigitslà tích cực, nhưng thật khó chịu là bạn không thể sử dụng nó để thay thế một cái gì đó như thế nào round(5, -1).
Pekka Klärck

15

Chỉ cần thêm vào đây một lưu ý quan trọng từ tài liệu:

https://docs.python.org/dev/l Library / fiances.html #round

Ghi chú

Hành vi của vòng () đối với phao có thể gây ngạc nhiên: ví dụ: vòng (2.675, 2) cho 2,67 thay vì 2,68 như mong đợi. Đây không phải là một lỗi: đó là kết quả của thực tế là hầu hết các phân số thập phân không thể được biểu diễn chính xác dưới dạng nổi. Xem Số học dấu phẩy động: Các vấn đề và giới hạn để biết thêm thông tin.

Vì vậy, đừng ngạc nhiên khi nhận được kết quả sau trong Python 3.2:

>>> round(0.25,1), round(0.35,1), round(0.45,1), round(0.55,1)
(0.2, 0.3, 0.5, 0.6)

>>> round(0.025,2), round(0.035,2), round(0.045,2), round(0.055,2)
(0.03, 0.04, 0.04, 0.06)

Tôi đã thấy điều đó. Và phản ứng đầu tiên của tôi: Ai đang sử dụng CPU 16 bit không có khả năng đại diện cho tất cả các hoán vị của "2,67x"? Nói rằng các phân số không thể được biểu thị bằng float dường như là một vật tế thần ở đây: không có CPU hiện đại nào không chính xác, trong BẤT K lang ngôn ngữ nào (ngoại trừ Python?)
Adam

9
@Adam: Tôi nghĩ bạn đang hiểu lầm. Định dạng nhị phân (IEEE 754 binary64) được sử dụng để lưu trữ số float không thể biểu thị 2.675chính xác: máy tính gần nhất có thể nhận được là 2.67499999999999982236431605997495353221893310546875. Điều đó khá gần, nhưng nó không chính xác bằng 2.675: nó rất gần với 2.67hơn một chút2.68 . Vì vậy, roundhàm thực hiện đúng và làm tròn nó với giá trị 2 chữ số sau điểm gần hơn, cụ thể là 2.67. Điều này không liên quan gì đến Python và mọi thứ liên quan đến dấu phẩy động nhị phân.
Đánh dấu Dickinson

3
Đó không phải là "điều đúng đắn" bởi vì nó được cấp một hằng số mã nguồn :), nhưng tôi thấy quan điểm của bạn.
Adam

@Adam: Trước đây tôi đã gặp phải sự khó chịu này trong JS nên nó không phải là ngôn ngữ cụ thể.
Igor

5

Gần đây tôi cũng có vấn đề với điều này. Do đó, tôi đã phát triển một mô-đun python 3 có 2 chức năng trueround () và trueround_precision () giải quyết vấn đề này và đưa ra hành vi làm tròn tương tự được sử dụng từ trường tiểu học (không phải làm tròn ngân hàng). Đây là mô-đun. Chỉ cần lưu mã và sao chép nó vào hoặc nhập nó. Lưu ý: mô-đun trueround_precision có thể thay đổi hành vi làm tròn tùy theo nhu cầu theo các mô-đun ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_ Đối với các chức năng bên dưới, hãy xem các tài liệu hoặc sử dụng trợ giúp (trueround) và trợ giúp (trueround_precision) nếu được sao chép vào một trình thông dịch để có thêm tài liệu.

#! /usr/bin/env python3
# -*- coding: utf-8 -*-

def trueround(number, places=0):
    '''
    trueround(number, places)

    example:

        >>> trueround(2.55, 1) == 2.6
        True

    uses standard functions with no import to give "normal" behavior to 
    rounding so that trueround(2.5) == 3, trueround(3.5) == 4, 
    trueround(4.5) == 5, etc. Use with caution, however. This still has 
    the same problem with floating point math. The return object will 
    be type int if places=0 or a float if places=>1.

    number is the floating point number needed rounding

    places is the number of decimal places to round to with '0' as the
        default which will actually return our interger. Otherwise, a
        floating point will be returned to the given decimal place.

    Note:   Use trueround_precision() if true precision with
            floats is needed

    GPL 2.0
    copywrite by Narnie Harshoe <signupnarnie@gmail.com>
    '''
    place = 10**(places)
    rounded = (int(number*place + 0.5if number>=0 else -0.5))/place
    if rounded == int(rounded):
        rounded = int(rounded)
    return rounded

def trueround_precision(number, places=0, rounding=None):
    '''
    trueround_precision(number, places, rounding=ROUND_HALF_UP)

    Uses true precision for floating numbers using the 'decimal' module in
    python and assumes the module has already been imported before calling
    this function. The return object is of type Decimal.

    All rounding options are available from the decimal module including 
    ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, 
    ROUND_HALF_UP, ROUND_UP, and ROUND_05UP.

    examples:

        >>> trueround(2.5, 0) == Decimal('3')
        True
        >>> trueround(2.5, 0, ROUND_DOWN) == Decimal('2')
        True

    number is a floating point number or a string type containing a number on 
        on which to be acted.

    places is the number of decimal places to round to with '0' as the default.

    Note:   if type float is passed as the first argument to the function, it
            will first be converted to a str type for correct rounding.

    GPL 2.0
    copywrite by Narnie Harshoe <signupnarnie@gmail.com>
    '''
    from decimal import Decimal as dec
    from decimal import ROUND_HALF_UP
    from decimal import ROUND_CEILING
    from decimal import ROUND_DOWN
    from decimal import ROUND_FLOOR
    from decimal import ROUND_HALF_DOWN
    from decimal import ROUND_HALF_EVEN
    from decimal import ROUND_UP
    from decimal import ROUND_05UP

    if type(number) == type(float()):
        number = str(number)
    if rounding == None:
        rounding = ROUND_HALF_UP
    place = '1.'
    for i in range(places):
        place = ''.join([place, '0'])
    return dec(number).quantize(dec(place), rounding=rounding)

Hi vọng điêu nay co ich,

Narnie


5

Python 3.x làm tròn 0,5 giá trị cho hàng xóm chẵn

assert round(0.5) == 0
assert round(1.5) == 2
assert round(2.5) == 2

import decimal

assert decimal.Decimal('0.5').to_integral_value() == 0
assert decimal.Decimal('1.5').to_integral_value() == 2
assert decimal.Decimal('2.5').to_integral_value() == 2

tuy nhiên, người ta có thể thay đổi "làm tròn" thập phân thành luôn làm tròn .5 lên, nếu cần:

decimal.getcontext().rounding = decimal.ROUND_HALF_UP

assert decimal.Decimal('0.5').to_integral_value() == 1
assert decimal.Decimal('1.5').to_integral_value() == 2
assert decimal.Decimal('2.5').to_integral_value() == 3

i = int(decimal.Decimal('2.5').to_integral_value()) # to get an int
assert i == 3
assert type(i) is int

1

Hành vi làm tròn Python 2 trong python 3.

Thêm 1 ở vị trí thập phân thứ 15. Độ chính xác lên tới 15 chữ số.

round2=lambda x,y=None: round(x+1e-15,y)

3
Bạn có thể giải thích trực giác đằng sau công thức này?
Hadi

2
Theo những gì tôi hiểu, các phân số không thể được trình bày chính xác sẽ có tới 15 9 giây, sau đó là sự không chính xác. Ví dụ, 2.6752.67499999999999982236431605997495353221893310546875. Thêm 1e-15 sẽ đưa nó lên trên 2.675 và làm tròn chính xác. nếu phân số đã vượt quá mã không đổi, việc thêm 1e-15 sẽ không thay đổi gì đối với làm tròn số.
Benoit Dufresne

1

Một số trường hợp:

in: Decimal(75.29 / 2).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
in: round(75.29 / 2, 2)
out: 37.65 GOOD

in: Decimal(85.55 / 2).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
in: round(85.55 / 2, 2)
out: 42.77 BAD

Để khắc phục:

in: round(75.29 / 2 + 0.00001, 2)
out: 37.65 GOOD
in: round(85.55 / 2 + 0.00001, 2)
out: 42.78 GOOD

Nếu bạn muốn có nhiều số thập phân hơn, ví dụ 4, bạn nên thêm (+ 0,0000001).

Làm việc cho tôi.


Đây là giải pháp duy nhất hiệu quả với tôi, cảm ơn vì đã đăng. Mọi người dường như có ý định làm tròn 0,5 lên / xuống, vì vậy tôi không thể quản lý các vấn đề làm tròn số thập phân.
Gayathri

-1

Sinh sản mẫu:

['{} => {}'.format(x+0.5, round(x+0.5)) for x in range(10)]

['0.5 => 0', '1.5 => 2', '2.5 => 2', '3.5 => 4', '4.5 => 4', '5.5 => 6', '6.5 => 6', '7.5 => 8', '8.5 => 8', '9.5 => 10']

API: https://docs.python.org/3/l Library / fiances.html #round

Những trạng thái:

Trả về số được làm tròn đến độ chính xác ndigits sau dấu thập phân. Nếu ndigits bị bỏ qua hoặc là Không có, nó sẽ trả về số nguyên gần nhất cho đầu vào của nó.

Đối với các loại tích hợp hỗ trợ round (), các giá trị được làm tròn đến bội số gần nhất của 10 với công suất trừ ndigits; nếu hai bội số gần nhau như nhau, làm tròn được thực hiện theo lựa chọn chẵn (ví dụ: cả vòng (0,5) và vòng (-0,5) là 0 và vòng (1,5) là 2). Bất kỳ giá trị số nguyên nào cũng hợp lệ cho ndigits (dương, không hoặc âm). Giá trị trả về là một số nguyên nếu ndigits bị bỏ qua hoặc Không có. Nếu không, giá trị trả về có cùng loại với số.

Đối với một số đối tượng Python chung, làm tròn số đại biểu thành số. tròn .

Lưu ý Hành vi của vòng () đối với phao có thể gây ngạc nhiên: ví dụ: vòng (2.675, 2) cho 2.67 thay vì 2.68 như mong đợi. Đây không phải là một lỗi: đó là kết quả của thực tế là hầu hết các phân số thập phân không thể được biểu diễn chính xác dưới dạng nổi. Xem Số học dấu phẩy động: Các vấn đề và giới hạn để biết thêm thông tin.

Với cái nhìn sâu sắc này, bạn có thể sử dụng một số toán học để giải quyết nó

import math
def my_round(i):
  f = math.floor(i)
  return f if i - f < 0.5 else f+1

bây giờ bạn có thể chạy thử nghiệm tương tự với my_round thay vì vòng.

['{} => {}'.format(x + 0.5, my_round(x+0.5)) for x in range(10)]
['0.5 => 1', '1.5 => 2', '2.5 => 3', '3.5 => 4', '4.5 => 5', '5.5 => 6', '6.5 => 7', '7.5 => 8', '8.5 => 9', '9.5 => 10']

-2

Cách dễ nhất để làm tròn trong Python 3.x như được dạy ở trường là sử dụng biến phụ trợ:

n = 0.1 
round(2.5 + n)

Và đây sẽ là kết quả của loạt 2.0 đến 3.0 (trong 0,1 bước):

>>> round(2 + n)
>>> 2

>>> round(2.1 + n)
>>> 2

>>> round(2.2 + n)
>>> 2

>>> round(2.3 + n)
>>> 2

>>> round(2.4 + n)
>>> 2

>>> round(2.5 + n)
>>> 3

>>> round(2.6 + n)
>>> 3

>>> round(2.7 + n)
>>> 3

>>> round(2.8 + n)
>>> 3

>>> round(2.9 + n)
>>> 3

>>> round(3 + n)
>>> 3

-2

Bạn có thể điều khiển làm tròn bạn bằng mô-đun math.ceil:

import math
print(math.ceil(2.5))
> 3

Điều đó sẽ luôn trả về số mà không có phần thập phân của nó, đây không phải là làm tròn. trần (2,5) = 2, trần (2,99) = 2
sau

1
trong python3 +, nếu đối số số là số dương hoặc số âm, hàm trần sẽ trả về giá trị trần.
Eds_k

Trong [14]: math.ceil (2.99) Out [14]: 3
Eds_k

Vâng, tôi xin lỗi tôi đã sai. Ceil () trả về giá trị trần trong khi floor () trả về giá trị mà tôi đang nói. Tuy nhiên, theo tôi, đây không hoàn toàn là hành vi làm tròn (cả hai chức năng này)
sau

-4

Hãy thử mã này:

def roundup(input):   
   demo = input  if str(input)[-1] != "5" else str(input).replace("5","6")
   place = len(demo.split(".")[1])-1
   return(round(float(demo),place))

Kết quả sẽ là:

>>> x = roundup(2.5)
>>> x
3.0  
>>> x = roundup(2.05)
>>> x
2.1 
>>> x = roundup(2.005)
>>> x
2.01 

Ooutput bạn có thể kiểm tra tại đây: https://i.stack.imgur.com/QQUkS.png

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.