Wydaje się, że round () nie zaokrągla się prawidłowo


123

Dokumentacja funkcji round () stwierdza, że ​​należy przekazać jej liczbę, a pozycje po przecinku do zaokrąglenia. Dlatego powinien to zrobić:

n = 5.59
round(n, 1) # 5.6

Ale w rzeczywistości wkrada się stara, dobra dziwność zmiennoprzecinkowa i dostajesz:

5.5999999999999996

Na potrzeby interfejsu użytkownika muszę wyświetlić 5.6. Przeszukałem Internet i znalazłem dokumentację, że jest to zależne od mojej implementacji Pythona. Niestety dzieje się tak zarówno na moim komputerze deweloperskim z systemem Windows, jak i na każdym serwerze Linux, którego próbowałem. Zobacz także tutaj .

Poza utworzeniem własnej okrągłej biblioteki, czy można to obejść?


4
Próbowałem to z python 2.7.11 rundzie (5,59) i to daje wynik 5,6 jak w Windows i Linux x86 64-bitowym komputerze, Cython (link dokumentacji wspomniano zmienia się teraz chyba)?
Alex Punnen

2
Tam, gdzie faktycznie nie działa poprawnie, jest round(5.55, 1) = 5.5.
Dmitry

Odpowiedzi:


102

Nie mogę pomóc w sposobie jego przechowywania, ale przynajmniej formatowanie działa poprawnie:

'%.1f' % round(n, 1) # Gives you '5.6'

11
próbowałem, print '%.2f' % 655.665ale wraca 655.66, powinno być655.67
Liza

1
@Kyrie patrz stackoverflow.com/questions/9301690/… . Winna jest tutaj niedokładność zmiennoprzecinkowa - „5,665 -> 5,67”, ale „15,665 -> 15,66”. Jeśli potrzebujesz dokładnej precyzji, użyj liczb dziesiętnych.
Jimmy

7
to działa po wyszukaniu :) from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_DOWN# użycie w zaokrąglaniu liczb zmiennoprzecinkowych Decimal(str(655.665)).quantize(Decimal('1.11'), rounding=ROUND_HALF_UP)# Problemy i ograniczenia w liczbach zmiennoprzecinkowych
Liza

102

Formatowanie działa poprawnie, nawet bez konieczności zaokrąglania:

"%.1f" % n

18
Zgodnie z dokumentacją ten styl formatowania ciągów ostatecznie zniknie. Nowy format to"{:.1f}".format(n)
whereswalden

2
Nie zaokrągla poprawnie: '%.5f' % 0.988625daje0.98862
schlamar

@schlamar: To też jest zachowanie round (): round (0.988625,5) również daje 0.98862. round (0.988626,5), a także "% .5f"% 0.988626 daje 0.98863
Vinko Vrsalovic.

niestety "% .2f"% 2.675 zwróci wartość 2,67 - co może być nieoczekiwaną odpowiedzią dla osób używających tej metody i oczekujących 2,68
Dion

30

Jeśli korzystasz z modułu Decimal, możesz przybliżać dane bez użycia funkcji „round”. Oto, czego używałem do zaokrąglania, szczególnie podczas pisania aplikacji pieniężnych:

Decimal(str(16.2)).quantize(Decimal('.01'), rounding=ROUND_UP)

To zwróci liczbę dziesiętną, która wynosi 16,20.


4
To jest kanoniczna odpowiedź - w każdym razie tam, gdzie dokładność ma znaczenie, co jest prawie wszędzie. Jasne: jest trochę rozwlekły . Ale wrzuć tę przyssawkę do funkcji pomocniczej i możesz sformatować i zacząć.
Cecil Curry

2
rounding='ROUND_UP'
LMc

Jeśli pojawi się ten błąd NameError: global name 'ROUND_UP' is not definedtrzeba zaimportować funkcję zaokrąglania: from decimal import Decimal, ROUND_UP. Inne funkcje zaokrąglania
Stephen Blair

Twój przykład wydaje się nadal niebezpieczny: polegasz na zaokrągleniu zapewnianym przez str ().
YvesgereY

21

round(5.59, 1)działa dobrze. Problem polega na tym, że 5.6 nie można dokładnie przedstawić w postaci binarnej zmiennoprzecinkowej.

>>> 5.6
5.5999999999999996
>>> 

Jak mówi Vinko, możesz użyć formatowania ciągów do zaokrąglania wyświetlania.

Python ma moduł do arytmetyki dziesiętnej, jeśli tego potrzebujesz.


1
Nie jest to już problem z Pythonem 2.7 lub Pythonem 3.5
vy32


10

Możesz zmienić typ danych na liczbę całkowitą:

>>> n = 5.59
>>> int(n * 10) / 10.0
5.5
>>> int(n * 10 + 0.5)
56

Następnie wyświetl liczbę, wstawiając separator dziesiętny dla ustawień regionalnych.

Jednak odpowiedź Jimmy'ego jest lepsza.


5

Matematyka zmiennoprzecinkowa jest podatna na drobne, ale irytujące, niedokładności precyzji. Jeśli możesz pracować z liczbami całkowitymi lub stałymi punktami, będziesz mieć gwarancję precyzji.


5

Spójrz na moduł Decimal

Dziesiętny „opiera się na modelu zmiennoprzecinkowym, który został zaprojektowany z myślą o ludziach i koniecznie ma nadrzędną zasadę przewodnią - komputery muszą zapewniać arytmetykę działającą w taki sam sposób, jak arytmetyka, której uczą się w szkole”. - wyciąg ze specyfikacji arytmetycznej dziesiętnej.

i

Liczby dziesiętne można przedstawić dokładnie. Natomiast liczby takie jak 1.1 i 2.2 nie mają dokładnej reprezentacji w postaci binarnej zmiennoprzecinkowej. Użytkownicy końcowi zazwyczaj nie oczekują, że 1.1 + 2.2 wyświetli się jako 3.3000000000000003, jak ma to miejsce w przypadku binarnych liczb zmiennoprzecinkowych.

Dziesiętne zapewnia rodzaj operacji, które ułatwiają pisanie aplikacji, które wymagają operacji zmiennoprzecinkowych, a także muszą przedstawiać te wyniki w formacie czytelnym dla człowieka, np. Księgowość.



4

To rzeczywiście duży problem. Wypróbuj ten kod:

print "%.2f" % (round((2*4.4+3*5.6+3*4.4)/8,2),)

Wyświetla 4,85. Następnie robisz:

print "Media = %.1f" % (round((2*4.4+3*5.6+3*4.4)/8,1),)

i pokazuje 4.8. Czy wykonujesz ręczne obliczenia, dokładna odpowiedź to 4,85, ale jeśli spróbujesz:

print "Media = %.20f" % (round((2*4.4+3*5.6+3*4.4)/8,20),)

możesz zobaczyć prawdę: punkt zmiennoprzecinkowy jest przechowywany jako najbliższa skończona suma ułamków, których mianownikami są potęgi dwóch.


3

Możesz użyć operatora formatu ciągu %, podobnie do sprintf.

mystring = "%.2f" % 5.5999


2

Robię:

int(round( x , 0))

W tym przypadku najpierw prawidłowo zaokrąglamy na poziomie jednostki, a następnie konwertujemy na liczbę całkowitą, aby uniknąć drukowania liczby zmiennoprzecinkowej.

więc

>>> int(round(5.59,0))
6

Myślę, że ta odpowiedź działa lepiej niż formatowanie ciągu, a także bardziej sensowne jest użycie funkcji round.


2

round()W tym przypadku w ogóle bym się nie opierał . Rozważać

print(round(61.295, 2))
print(round(1.295, 2))

wyjdzie

61.3
1.29

co nie jest pożądanym wynikiem, jeśli potrzebujesz pełnego zaokrąglenia do najbliższej liczby całkowitej. Aby ominąć to zachowanie, użyj math.ceil()(lub math.floor()jeśli chcesz zaokrąglić w dół):

from math import ceil
decimal_count = 2
print(ceil(61.295 * 10 ** decimal_count) / 10 ** decimal_count)
print(ceil(1.295 * 10 ** decimal_count) / 10 ** decimal_count)

wyjścia

61.3
1.3

Mam nadzieję, że to pomoże.


1

Kod:

x1 = 5.63
x2 = 5.65
print(float('%.2f' % round(x1,1)))  # gives you '5.6'
print(float('%.2f' % round(x2,1)))  # gives you '5.7'

Wynik:

5.6
5.7

0

Oto, gdzie widzę, że runda zawodzi. A co by było, gdybyś chciał zaokrąglić te dwie liczby do jednego miejsca po przecinku? 23,45 23,55 Moje wykształcenie było takie, że z zaokrąglenia tych wyników powinno się otrzymać: 23,4 23,6 „zasadą” jest zaokrąglanie w górę, jeśli poprzednia liczba była nieparzysta, a nie zaokrąglanie w górę, jeśli poprzednia liczba była parzysta. Funkcja round w Pythonie po prostu obcina 5.


1
To, o czym mówisz, to „zaokrąglanie bankierów” , jeden z wielu sposobów wykonywania zaokrąglania.
Simon MᶜKenzie,

0

Problem występuje tylko wtedy, gdy ostatnia cyfra to 5. Np. 0,045 jest wewnętrznie zapisywane jako 0,044999999999999 ... Można po prostu zwiększyć ostatnią cyfrę do 6 i zaokrąglić. To da pożądane rezultaty.

import re


def custom_round(num, precision=0):
    # Get the type of given number
    type_num = type(num)
    # If the given type is not a valid number type, raise TypeError
    if type_num not in [int, float, Decimal]:
        raise TypeError("type {} doesn't define __round__ method".format(type_num.__name__))
    # If passed number is int, there is no rounding off.
    if type_num == int:
        return num
    # Convert number to string.
    str_num = str(num).lower()
    # We will remove negative context from the number and add it back in the end
    negative_number = False
    if num < 0:
        negative_number = True
        str_num = str_num[1:]
    # If number is in format 1e-12 or 2e+13, we have to convert it to
    # to a string in standard decimal notation.
    if 'e-' in str_num:
        # For 1.23e-7, e_power = 7
        e_power = int(re.findall('e-[0-9]+', str_num)[0][2:])
        # For 1.23e-7, number = 123
        number = ''.join(str_num.split('e-')[0].split('.'))
        zeros = ''
        # Number of zeros = e_power - 1 = 6
        for i in range(e_power - 1):
            zeros = zeros + '0'
        # Scientific notation 1.23e-7 in regular decimal = 0.000000123
        str_num = '0.' + zeros + number
    if 'e+' in str_num:
        # For 1.23e+7, e_power = 7
        e_power = int(re.findall('e\+[0-9]+', str_num)[0][2:])
        # For 1.23e+7, number_characteristic = 1
        # characteristic is number left of decimal point.
        number_characteristic = str_num.split('e+')[0].split('.')[0]
        # For 1.23e+7, number_mantissa = 23
        # mantissa is number right of decimal point.
        number_mantissa = str_num.split('e+')[0].split('.')[1]
        # For 1.23e+7, number = 123
        number = number_characteristic + number_mantissa
        zeros = ''
        # Eg: for this condition = 1.23e+7
        if e_power >= len(number_mantissa):
            # Number of zeros = e_power - mantissa length = 5
            for i in range(e_power - len(number_mantissa)):
                zeros = zeros + '0'
            # Scientific notation 1.23e+7 in regular decimal = 12300000.0
            str_num = number + zeros + '.0'
        # Eg: for this condition = 1.23e+1
        if e_power < len(number_mantissa):
            # In this case, we only need to shift the decimal e_power digits to the right
            # So we just copy the digits from mantissa to characteristic and then remove
            # them from mantissa.
            for i in range(e_power):
                number_characteristic = number_characteristic + number_mantissa[i]
            number_mantissa = number_mantissa[i:]
            # Scientific notation 1.23e+1 in regular decimal = 12.3
            str_num = number_characteristic + '.' + number_mantissa
    # characteristic is number left of decimal point.
    characteristic_part = str_num.split('.')[0]
    # mantissa is number right of decimal point.
    mantissa_part = str_num.split('.')[1]
    # If number is supposed to be rounded to whole number,
    # check first decimal digit. If more than 5, return
    # characteristic + 1 else return characteristic
    if precision == 0:
        if mantissa_part and int(mantissa_part[0]) >= 5:
            return type_num(int(characteristic_part) + 1)
        return type_num(characteristic_part)
    # Get the precision of the given number.
    num_precision = len(mantissa_part)
    # Rounding off is done only if number precision is
    # greater than requested precision
    if num_precision <= precision:
        return num
    # Replace the last '5' with 6 so that rounding off returns desired results
    if str_num[-1] == '5':
        str_num = re.sub('5$', '6', str_num)
    result = round(type_num(str_num), precision)
    # If the number was negative, add negative context back
    if negative_number:
        result = result * -1
    return result

0

Inną potencjalną opcją jest:

def hard_round(number, decimal_places=0):
    """
    Function:
    - Rounds a float value to a specified number of decimal places
    - Fixes issues with floating point binary approximation rounding in python
    Requires:
    - `number`:
        - Type: int|float
        - What: The number to round
    Optional:
    - `decimal_places`:
        - Type: int 
        - What: The number of decimal places to round to
        - Default: 0
    Example:
    ```
    hard_round(5.6,1)
    ```
    """
    return int(number*(10**decimal_places)+0.5)/(10**decimal_places)

-4

Co powiesz na:

round(n,1)+epsilon

To zadziałałoby tylko wtedy, gdyby zaokrąglanie było konsekwentnie odchylane od okrągłej liczby o epsilon. Gdyby epsilon = .000001zatem round(1.0/5.0, 1) + epsilonprzyjąć dokładną reprezentację 0,2 i uczynić z niej 0,00001. Równie poważne problemy wystąpiłyby, gdyby epsilon znajdował się wewnątrz funkcji rundy.
Michael Scott Cuthbert,
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.