Zachowanie zaokrąglania w Pythonie 3.x.


176

Właśnie ponownie przeczytałem Co nowego w Pythonie 3.0 i stwierdza:

Zmieniono strategię zaokrąglania funkcji round () i typ zwracania. Dokładne przypadki w połowie są teraz zaokrąglane do najbliższego parzystego wyniku zamiast od zera. (Na przykład round (2.5) zwraca teraz 2 zamiast 3).

oraz dokumentacja do rundy :

W przypadku typów wbudowanych obsługujących funkcję round () wartości są zaokrąglane do najbliższej wielokrotności 10 do potęgi minus n; jeśli dwie wielokrotności są jednakowo zbliżone, następuje zaokrąglenie w kierunku parzystego wyboru

Tak więc w wersji 2.7.3 :

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

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

jak bym się spodziewał. Jednak teraz w wersji 3.2.3 :

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

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

Wydaje się to sprzeczne z intuicją i sprzeczne z tym, co rozumiem o zaokrąglaniu (i potykaniu się o ludzi). Angielski nie jest moim językiem ojczystym, ale dopóki tego nie przeczytałem, wydawało mi się, że wiem, co oznacza zaokrąglenie: - / Jestem pewien, że w momencie wprowadzenia wersji 3 musiała o tym rozmawiać, ale nie mogłem znaleźć dobrego powodu w moje wyszukiwanie.

  1. Czy ktoś ma wgląd w to, dlaczego zmieniono to na to?
  2. Czy są jakieś inne popularne języki programowania (np. C, C ++, Java, Perl, ...), które wykonują tego rodzaju (dla mnie niespójne) zaokrąglanie?

Czego tu brakuje?

AKTUALIZACJA: @ Li-aungYip w komentarzu dotyczącym „Zaokrąglanie bankierów” dało mi właściwe wyszukiwane hasło / słowa kluczowe do wyszukania i znalazłem następujące pytanie SO: Dlaczego .NET domyślnie używa zaokrąglania bankierów? , więc będę to uważnie czytał.


27
Nie mam czasu, żeby to sprawdzić, ale wydaje mi się, że nazywa się to „zaokrąglaniem bankierów”. Uważam, że jest to powszechne w branży finansowej.
Li-aung Yip

2
@sberry cóż, tak, jego zachowanie jest zgodne z własnym opisem. Gdyby więc powiedział, że „zaokrąglanie” to podwojenie jego wartości i zrobiłoby to, byłoby to również spójne :) .. ale wydaje się sprzeczne z tym, co zwykle oznacza zaokrąglanie . Więc szukam lepszego zrozumienia.
Levon

1
@ Li-aungYip Dzięki za prowadzenie w sprawie „Zaokrąglanie bankierów” .. Sprawdzę to.
Levon


3
Uwaga: zaokrąglanie bankierów nie jest powszechne tylko w finansach. Tak nauczyłem się
kręcić

Odpowiedzi:


160

Sposób Pythona 3.0 jest obecnie uważany za standardową metodę zaokrąglania, chociaż niektóre implementacje językowe nie są jeszcze dostępne w magistrali.

Prosta technika „zawsze zaokrąglaj 0,5 w górę” skutkuje lekkim odchyleniem w kierunku wyższej liczby. W przypadku dużej liczby obliczeń może to być znaczące. Podejście Python 3.0 eliminuje ten problem.

W powszechnym użyciu jest więcej niż jedna metoda zaokrąglania. IEEE 754, międzynarodowy standard matematyki zmiennoprzecinkowej, definiuje pięć różnych metod zaokrąglania (domyślną jest ta używana w Pythonie 3.0). I są inni.

To zachowanie nie jest tak szeroko znane, jak powinno. AppleScript był, o ile dobrze pamiętam, jednym z pierwszych użytkowników tej metody zaokrąglania. roundKomenda w AppleScript faktycznie daje kilka opcji, ale round-ku-nawet jest domyślnym, jak to jest w standardzie IEEE 754. Widocznie inżynier, który wdrożył roundpolecenie dostał więc dość wszystkich żądań do „sprawiają, że działa jak dowiedziałem się school ”, którą właśnie zaimplementował: round 2.5 rounding as taught in schooljest poprawnym poleceniem AppleScript. :-)


4
Nie byłem świadomy tej „domyślnej standardowej metody zaokrąglania prawie powszechnie w dzisiejszych czasach”, czy Ty (lub ktokolwiek inny) wiedziałbyś, czy C / C ++ / Java / Perl lub inne języki „głównego nurtu” implementują zaokrąglanie w ten sam sposób?
Levon

3
Ruby to robi. Robią to języki Microsoft .NET. Jednak Java nie wydaje się. Nie mogę go znaleźć dla każdego możliwego języka, ale wydaje mi się, że jest to najbardziej powszechne w dość niedawno zaprojektowanych językach. Wyobrażam sobie, że C i C ++ są na tyle stare, że nie.
kindall

5
ruby wraca 3do2.5.round
jfs

14
Dodałem trochę o obsłudze tego przez AppleScript, ponieważ uwielbiam sarkastyczny sposób zaimplementowania „starego” zachowania.
kindall

2
@kindall Ta metoda jest domyślnym trybem zaokrąglania IEEE od 1985 roku (kiedy opublikowano IEEE 754-1985). Był to również domyślny tryb zaokrąglania w C od co najmniej C89 (a więc także w C ++), jednak od czasu C99 (i C ++ 11 z wcześniej sporadyczną obsługą) dostępna jest funkcja "round ()", która używa zamiast tego krawaty są zaokrąglane od zera. Wewnętrzne zaokrąglanie zmiennoprzecinkowe i rodzina funkcji rint () nadal są zgodne z ustawieniem trybu zaokrąglania, który domyślnie jest zaokrąglany do parzystości.
Wlerin

41

Możesz kontrolować zaokrąglanie uzyskiwane w Py3000 za pomocą modułu Decimal :

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

Dzięki… nie znałem tego modułu. Masz jakiś pomysł, jak uzyskać zachowanie Pythona v 2.x? Przykłady, które pokazujesz, wydają się tego nie robić. Ciekawe, czy to byłoby możliwe.
Levon

1
@Levon: Stała ROUND_HALF_UPjest taka sama, jak stare zachowanie Pythona 2.X.
świt

2
Możesz również ustawić kontekst dla modułu Decimal, który robi to niejawnie za Ciebie. Zobacz setcontext()funkcję.
kindall

Właśnie tego szukałem dzisiaj. Działa zgodnie z oczekiwaniami w Pythonie 3.4.3. Warto również zauważyć, można kontrolować, ile rund zmieniając quantize(decimal.Decimal('1')się quantize(decimal.Decimal('0.00')jeśli chcesz zaokrąglenie do najbliższej 100s, takich jak jakość.
Igor

To rozwiązanie działa jako zamiennik round(number, ndigits)tak długo, jak ndigitsjest pozytywne, ale irytująco nie można go użyć do zastąpienia czegoś podobnego round(5, -1).
Pekka Klärck

15

Wystarczy dodać tutaj ważną notatkę z dokumentacji:

https://docs.python.org/dev/library/functions.html#round

Uwaga

Zachowanie round () dla pływaków może być zaskakujące: na przykład round (2,675, 2) daje 2,67 zamiast oczekiwanego 2,68. To nie jest błąd: wynika to z faktu, że większości ułamków dziesiętnych nie można przedstawić dokładnie jako liczby zmiennoprzecinkowej. Aby uzyskać więcej informacji, zobacz Arytmetyka zmiennoprzecinkowa: problemy i ograniczenia.

Więc nie zdziw się, jeśli uzyskasz następujące wyniki w Pythonie 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)

Widziałem to. I moja pierwsza reakcja: kto używa 16-bitowego procesora, który nie jest w stanie reprezentować wszystkich permutacji „2,67x”? Mówienie, że ułamków nie można wyrazić w float, wydaje się tutaj kozłem ofiarnym: żaden nowoczesny procesor nie jest tak niedokładny, w ŻADNYM języku (z wyjątkiem Pythona?)
Adam

9
@Adam: Myślę, że nie rozumiesz. Format binarny (IEEE 754 binary64) używany do przechowywania wartości zmiennoprzecinkowych nie może 2.675dokładnie reprezentować : najbliższy komputer może uzyskać 2.67499999999999982236431605997495353221893310546875. To dość blisko, ale nie równa się dokładnie2.675 : jest trochę bliżej 2.67niż do 2.68. Więc roundfunkcja robi właściwą rzecz i zaokrągla ją do bliższej dwucyfrowej wartości po punkcie, a mianowicie 2.67. Nie ma to nic wspólnego z Pythonem, a wszystko, co ma związek z binarnymi liczbami zmiennoprzecinkowymi.
Mark Dickinson,

3
To nie jest „właściwa rzecz”, ponieważ otrzymała stałą kodu źródłowego :), ale rozumiem, o co chodzi.
Adam

@Adam: Już wcześniej spotkałem się z tą samą dziwactwem w JS, więc nie jest to specyficzne dla języka.
Igor

5

Ja też ostatnio miałem z tym problemy. W związku z tym opracowałem moduł Pythona 3, który ma 2 funkcje trueround () i trueround_precision (), które rozwiązują ten problem i zapewniają takie samo zaokrąglanie, do jakiego przyzwyczailiśmy się w szkole podstawowej (a nie zaokrąglanie bankierów). Oto moduł. Po prostu zapisz kod i skopiuj go lub zaimportuj. Uwaga: moduł trueround_precision może zmienić zachowanie zaokrąglania w zależności od potrzeb zgodnie z flagami ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_UP i ROUND_05UP w module dziesiętnym (więcej informacji w tej dokumentacji). Aby zapoznać się z poniższymi funkcjami, zapoznaj się z dokumentacją lub użyj help (trueround) i help (trueround_precision), jeśli zostaną skopiowane do interpretera w celu dalszej dokumentacji.

#! /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)

Mam nadzieję że to pomoże,

Narnie


5

Python 3.x zaokrągla wartości 0,5 do parzystego sąsiada

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

można jednak zmienić zaokrąglanie dziesiętne „z powrotem” na zawsze zaokrąglanie 0,5 w górę, w razie potrzeby:

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

Zachowanie zaokrąglania w Pythonie 2 w Pythonie 3.

Dodanie 1 na 15. miejscu po przecinku. Dokładność do 15 cyfr.

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

3
Czy mógłbyś wyjaśnić intuicję kryjącą się za tą formułą?
Hadi

2
Z tego, co rozumiem, ułamki, których nie można dokładnie przedstawić, będą miały do ​​15 9, a następnie niedokładność. Na przykład 2.675jest 2.67499999999999982236431605997495353221893310546875. Dodanie 1e-15 spowoduje przewrócenie wartości 2,675 i poprawne zaokrąglenie. jeśli ułamek jest już powyżej stałej kodu, dodanie 1e-15 nic nie zmieni w zaokrągleniu.
Benoit Dufresne

1

Niektóre przypadki:

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

Do naprawy:

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

Jeśli chcesz mieć więcej miejsc po przecinku, na przykład 4, dodaj (+ 0,0000001).

Pracuj dla mnie.


To było jedyne rozwiązanie, które działało dla mnie, dzięki za wysłanie. Wydaje się, że wszyscy są zainteresowani zaokrągleniem 0,5 w górę / w dół, więc nie mogłem poradzić sobie z problemami z zaokrąglaniem wielu miejsc dziesiętnych.
Gayathri,

-1

Reprodukcja próbek:

['{} => {}'.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/library/functions.html#round

Stany:

Zwraca liczbę zaokrągloną do dokładności ncyfrowej po przecinku. Jeśli ndigits zostanie pominięte lub ma wartość None, zwraca najbliższą liczbę całkowitą na wejściu.

W przypadku typów wbudowanych obsługujących funkcję round () wartości są zaokrąglane do najbliższej wielokrotności liczby 10 do potęgi minus ncyfry; jeśli dwie wielokrotności są jednakowo zbliżone, zaokrąglanie odbywa się w kierunku równego wyboru (na przykład zaokrąglenie (0,5) i zaokrąglenie (-0,5) to 0, a zaokrąglenie (1,5) to 2). Dowolna wartość całkowita jest poprawna dla ncyfr (dodatnia, zero lub ujemna). Wartość zwracana jest liczbą całkowitą, jeśli pominięto ndigits lub brak. W przeciwnym razie wartość zwracana ma ten sam typ co liczba.

W przypadku ogólnego numeru obiektu Pythona zaokrąglaj delegatów do liczby. okrągłe .

Uwaga Zachowanie round () dla liczb zmiennoprzecinkowych może być zaskakujące: na przykład round (2,675, 2) daje 2,67 zamiast oczekiwanego 2,68. To nie jest błąd: wynika to z faktu, że większości ułamków dziesiętnych nie można przedstawić dokładnie jako liczby zmiennoprzecinkowej. Aby uzyskać więcej informacji, zobacz Arytmetyka zmiennoprzecinkowa: problemy i ograniczenia.

Biorąc pod uwagę ten wgląd, możesz użyć matematyki, aby go rozwiązać

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

teraz możesz uruchomić ten sam test z my_round zamiast round.

['{} => {}'.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

Najłatwiejszym sposobem zaokrąglenia w Pythonie 3.x, jak uczy szkoła, jest użycie zmiennej pomocniczej:

n = 0.1 
round(2.5 + n)

A to będą wyniki serii od 2,0 do 3,0 (w krokach co 0,1):

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

Możesz kontrolować zaokrąglanie za pomocą modułu math.ceil:

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

To zawsze zwróci liczbę bez jej części dziesiętnej, to nie jest zaokrąglenie. ceil (2,5) = 2, ceil (2,99) = 2
krafter

1
w python3 +, jeśli argument liczbowy jest liczbą dodatnią lub ujemną, funkcja ceil zwraca wartość sufitu.
Eds_k

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

Tak, przepraszam, pomyliłem się. Ceil () zwraca wartość sufitu, podczas gdy floor () zwraca ten, o którym mówiłem. Ale nadal, moim zdaniem, nie jest to do końca zaokrąglanie (obie te funkcje)
krafter

-4

Wypróbuj ten kod:

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

Rezultatem będzie:

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

Wyjście możesz sprawdzić tutaj: https://i.stack.imgur.com/QQUkS.png

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.