round () scheint nicht richtig zu runden


123

In der Dokumentation für die Funktion round () wird angegeben, dass Sie eine Zahl und die Positionen nach der Dezimalstelle an round übergeben. Also sollte es dies tun:

n = 5.59
round(n, 1) # 5.6

Aber in Wirklichkeit schleicht sich eine gute alte Gleitkomma-Verrücktheit ein und Sie erhalten:

5.5999999999999996

Für die Zwecke der Benutzeroberfläche muss ich anzeigen 5.6. Ich habe mich im Internet umgesehen und eine Dokumentation gefunden , die von meiner Implementierung von Python abhängt. Leider tritt dies sowohl auf meinem Windows-Entwicklungscomputer als auch auf jedem Linux-Server auf, den ich ausprobiert habe. Siehe auch hier .

Gibt es einen Weg, dies zu umgehen, ohne meine eigene runde Bibliothek zu erstellen?


4
Ich habe dies mit Python 2.7.11 Round (5.59) versucht und es gibt das Ergebnis als 5.6 in Windows und Linux x86 64-Bit-Maschine, Cython? (Der erwähnte Dokumentationslink wird jetzt geändert, denke ich)
Alex Punnen

2
Wo es eigentlich nicht richtig funktioniert ist round(5.55, 1) = 5.5.
Dmitry

Antworten:


101

Ich kann nicht anders, als es gespeichert ist, aber zumindest funktioniert die Formatierung korrekt:

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

11
Ich habe es versucht, print '%.2f' % 655.665aber es kehrt zurück 655.66, es sollte sein655.67
Liza

1
@Kyrie siehe stackoverflow.com/questions/9301690/… . Hier ist die Gleitkomma-Ungenauigkeit schuld - "5,665 -> 5,67", aber "15,665 -> 15,66". Verwenden Sie Dezimalstellen, wenn Sie genaue Präzision benötigen.
Jimmy

7
Dies funktioniert nach der Suche :) from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_DOWN# Verwendung beim Runden von Gleitkommazahlen Decimal(str(655.665)).quantize(Decimal('1.11'), rounding=ROUND_HALF_UP)# Probleme und Einschränkungen in Gleitkommazahlen
Liza

102

Die Formatierung funktioniert auch ohne Runden:

"%.1f" % n

18
Laut den Dokumenten wird diese Art der Zeichenfolgenformatierung irgendwann verschwinden. Das neue Format wäre"{:.1f}".format(n)
Whereswalden

2
'%.5f' % 0.9886250.98862
Rundet

@schlamar: Das ist auch das Verhalten von round (): round (0.988625,5) ergibt ebenfalls 0.98862. Runde (0,988626,5) sowie "% .5f"% 0,988626 ergeben 0,98863
Vinko Vrsalovic

Leider wird "% .2f"% 2.675 2.67 zurückgeben - was eine unerwartete Antwort für diejenigen sein könnte, die diese Methode verwenden und 2.68 erwarten
Dion

30

Wenn Sie das Dezimalmodul verwenden, können Sie ohne Verwendung der Funktion 'rund' approximieren. Folgendes habe ich zum Runden verwendet, insbesondere beim Schreiben von Geldanträgen:

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

Dies gibt eine Dezimalzahl zurück, die 16,20 beträgt.


4
Dies ist die kanonische Antwort - wo Genauigkeit sowieso wichtig ist, was so ziemlich überall der Fall ist. Klar: es ist ein bisschen ausführlich . Aber werfen Sie diesen Trottel in eine Hilfsfunktion und Sie können gut formatieren und loslegen.
Cecil Curry

2
rounding='ROUND_UP'
LMc

Wenn Sie diesen Fehler erhalten NameError: global name 'ROUND_UP' is not defined, müssen Sie Ihre Rundungsfunktion importieren : from decimal import Decimal, ROUND_UP. Andere Rundungsfunktionen
Stephen Blair

Ihr Beispiel scheint immer noch gefährlich: Sie verlassen sich auf die Rundung von str ().
YvesgereY

21

round(5.59, 1)funktioniert gut. Das Problem ist, dass 5.6 nicht genau im binären Gleitkomma dargestellt werden kann.

>>> 5.6
5.5999999999999996
>>> 

Wie Vinko sagt, können Sie die Zeichenfolgenformatierung verwenden, um die Anzeige abzurunden.

Python hat ein Modul für Dezimalarithmetik, wenn Sie das brauchen.


1
Dies ist kein Problem mehr mit Python 2.7 oder Python 3.5
vy32


10

Sie können den Datentyp in eine Ganzzahl ändern:

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

Zeigen Sie dann die Nummer an, indem Sie das Dezimaltrennzeichen des Gebietsschemas einfügen.

Doch Jimmys Antwort ist besser.


5

Gleitkomma-Mathematik ist anfällig für leichte, aber ärgerliche Präzisionsungenauigkeiten. Wenn Sie mit Ganzzahlen oder Festpunkten arbeiten können, ist Ihnen Präzision garantiert.


5

Schauen Sie sich das Dezimalmodul an

Dezimalzahl „basiert auf einem Gleitkommamodell, das für Menschen entwickelt wurde und notwendigerweise ein vorrangiges Leitprinzip hat - Computer müssen eine Arithmetik bereitstellen, die genauso funktioniert wie die Arithmetik, die Menschen in der Schule lernen.“ - Auszug aus der dezimalen arithmetischen Spezifikation.

und

Dezimalzahlen können genau dargestellt werden. Im Gegensatz dazu haben Zahlen wie 1.1 und 2.2 keine exakten Darstellungen im binären Gleitkomma. Endbenutzer würden normalerweise nicht erwarten, dass 1.1 + 2.2 als 3.3000000000000003 angezeigt wird, wie dies beim binären Gleitkomma der Fall ist.

Dezimal bietet die Art von Operationen , die es einfach zu schreiben Anwendungen machen , die Floating - Point - Operationen erfordern und auch müssen diese Ergebnisse in einem für Menschen lesbares Format präsentieren, zum Beispiel Buchhaltung.



4

Das ist in der Tat ein großes Problem. Probieren Sie diesen Code aus:

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

Es zeigt 4.85 an. Dann machst du:

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

und es zeigt 4.8. Berechnen Sie von Hand, lautet die genaue Antwort 4,85, aber wenn Sie es versuchen:

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

Sie können die Wahrheit sehen: Der Gleitkomma wird als die nächste endliche Summe von Brüchen gespeichert, deren Nenner Zweierpotenzen sind.


3

Sie können den Zeichenfolgenformatoperator %ähnlich wie sprintf verwenden.

mystring = "%.2f" % 5.5999


2

Ich mache:

int(round( x , 0))

In diesem Fall runden wir zuerst auf Einheitenebene richtig und konvertieren dann in eine Ganzzahl, um das Drucken eines Gleitkommas zu vermeiden.

so

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

Ich denke, diese Antwort funktioniert besser als das Formatieren der Zeichenfolge, und es ist für mich auch sinnvoller, die Rundungsfunktion zu verwenden.


2

Ich würde es round()in diesem Fall vermeiden, mich überhaupt darauf zu verlassen . Erwägen

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

wird ausgegeben

61.3
1.29

Dies ist keine gewünschte Ausgabe, wenn Sie eine feste Rundung auf die nächste Ganzzahl benötigen. Um dieses Verhalten zu umgehen, gehen Sie mit math.ceil()(oder math.floor()wenn Sie abrunden möchten):

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)

Ausgänge

61.3
1.3

Hoffentlich hilft das.


1

Code:

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'

Ausgabe:

5.6
5.7

0

Hier sehe ich, dass die Runde versagt. Was wäre, wenn Sie diese beiden Zahlen auf eine Dezimalstelle runden möchten? 23.45 23.55 Meine Ausbildung war, dass Sie durch Runden dieser erhalten sollten: 23.4 23.6 Die "Regel" ist, dass Sie aufrunden sollten, wenn die vorhergehende Zahl ungerade war, nicht aufrunden, wenn die vorhergehende Zahl gerade war. Die Rundungsfunktion in Python schneidet einfach die 5 ab.


1
Sie sprechen von "Banker-Rundung" , einer von vielen verschiedenen Möglichkeiten, eine Rundung durchzuführen.
Simon MᶜKenzie

0

Das Problem ist nur, wenn die letzte Ziffer 5 ist. ZB. 0.045 wird intern als 0.04499999999999999 gespeichert ... Sie können die letzte Ziffer einfach auf 6 erhöhen und abrunden. Dadurch erhalten Sie die gewünschten Ergebnisse.

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

Eine weitere mögliche Option ist:

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

Wie wäre es mit:

round(n,1)+epsilon

Das würde nur funktionieren, wenn die Rundung von epsilon konsequent von der Rundenzahl abweicht. Wenn epsilon = .000001dann round(1.0/5.0, 1) + epsilonwürde die genaue Darstellung 0,2 nehmen und es 0,00001 machen. Ebenso schlimme Probleme würden auftreten, wenn sich das Epsilon innerhalb der runden Funktion befände.
Michael Scott Cuthbert
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.