Ich weiß, dass Fragen zum Runden in Python bereits mehrmals gestellt wurden, aber die Antworten haben mir nicht geholfen. Ich suche nach einer Methode, die eine Float-Nummer halbiert und eine Float-Nummer zurückgibt. Die Methode sollte auch einen Parameter akzeptieren, der die Dezimalstelle definiert, auf die gerundet werden soll. Ich habe eine Methode geschrieben, die diese Art der Rundung implementiert. Ich denke jedoch, dass es überhaupt nicht elegant aussieht.
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)
Ich mag es nicht, dass ich float in einen String konvertieren muss (um Gleitkomma-Ungenauigkeiten zu vermeiden) und dann mit dem Dezimalmodul arbeiten muss . Haben Sie bessere Lösungen?
Bearbeiten: Wie in den Antworten unten ausgeführt, ist die Lösung meines Problems nicht so offensichtlich, da eine korrekte Rundung in erster Linie die korrekte Darstellung von Zahlen erfordert und dies bei float nicht der Fall ist. Also ich würde erwarten, dass der folgende Code
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)
(das unterscheidet sich vom obigen Code nur dadurch, dass die Gleitkommazahl direkt in eine Dezimalzahl und nicht zuerst in eine Zeichenfolge konvertiert wird), um 2.18 zurückzugeben, wenn es so verwendet wird: round_half_up(2.175, 2)
Aber es wird nicht Decimal(2.175)
zurückgegeben Decimal('2.17499999999999982236431605997495353221893310546875')
, wie der Gleitkommawert Nummer wird vom Computer dargestellt. Überraschenderweise gibt der erste Code 2.18 zurück, da die Gleitkommazahl zuerst in einen String konvertiert wird. Es scheint, dass die Funktion str () eine implizite Rundung auf die Zahl durchführt, die ursprünglich gerundet werden sollte. Es finden also zwei Rundungen statt. Obwohl dies das Ergebnis ist, das ich erwarten würde, ist es technisch falsch.
quelle
Antworten:
Das Runden ist überraschend schwer richtig zu machen , da Sie Gleitkommaberechnungen sehr sorgfältig behandeln müssen. Wenn Sie nach einer eleganten Lösung suchen (kurz, leicht verständlich), ist das, was Sie mögen, ein guter Ausgangspunkt. Um korrekt zu sein, sollten Sie
decimal.Decimal(str(number))
die Dezimalstelle aus der Zahl selbst erstellen, um eine Dezimalversion der genauen Darstellung zu erhalten:Decimal(str(number))
rundet effektiv zweimal , da das Formatieren des Gleitkommas in die Zeichenfolgendarstellung eine eigene Rundung durchführt. Dies liegt daran,str(float value)
dass nicht versucht wird, die vollständige Dezimaldarstellung des Gleitkommas zu drucken, sondern nur dann genügend Ziffern gedruckt werden, um sicherzustellen, dass Sie denselben Gleitkommawert zurückerhalten, wenn Sie diese genauen Ziffern an denfloat
Konstruktor übergeben.Wenn Sie richtige Rundung behalten wollen, aber vermeiden Sie auf der großen und komplexen je
decimal
Modul können Sie sicher es tun, aber Sie müssen noch einige Weg , um die genaue arithmetics für die korrekte Rundung erforderlich umzusetzen. Beispielsweise können Sie Brüche verwenden :Beachten Sie, dass der einzige schwierige Teil im obigen Code die genaue Konvertierung eines Gleitkommas in einen Bruch ist und auf die Gleitkommamethode
as_integer_ratio()
entladen werden kann, was sowohl Dezimalstellen als auch Brüche intern tun. Wenn Sie also die Abhängigkeit von wirklich entfernen möchtenfractions
, können Sie die Brucharithmetik auf eine reine Ganzzahlarithmetik reduzieren. Sie bleiben auf Kosten der Lesbarkeit innerhalb der gleichen Zeilenanzahl:Beachten Sie, dass durch das Testen dieser Funktionen die beim Erstellen von Gleitkommazahlen durchgeführten Annäherungen hervorgehoben werden. Wird beispielsweise
print(round_half_up(2.175, 2))
gedruckt,2.17
weil die Dezimalzahl2.175
nicht exakt binär dargestellt werden kann. Sie wird daher durch eine Näherung ersetzt, die etwas kleiner als die Dezimalzahl von 2,175 ist. Die Funktion empfängt diesen Wert, findet ihn kleiner als den tatsächlichen Bruch, der der 2.175-Dezimalstelle entspricht, und beschließt, ihn abzurunden . Dies ist keine Eigenart der Implementierung. Das Verhalten leitet sich aus den Eigenschaften von Gleitkommazahlen ab und ist auchround
in Python 3 und 2 integriert .quelle
d = Decimal(number).quantize(...)
ist, dass es keine exakte Darstellung von Float-Zahlen gibt. SoDecimal(2.175)
geben Sie Dezimal ( ‚2,17499999 ...‘) (und somit wird eine falsche Rundung) , währendDecimal('2.175')
ist Dezimal ( ‚2.175‘). Deshalb konvertiere ich zuerst in einen String.as_integer_ratio()
(im Fall von 0,1, das wäre 3602879701896397/36028797018963968 sein). Derdecimal.Decimal(float)
Konstruktor verwendet diese Darstellung und die nachfolgende Rundung ist korrekt und wird genau ausgeführt.round_half_up(2.175, 2)
und sie 2.17 zurückgegeben hat, was falsch ist. Vielleicht mache ich noch etwas falsch?'%.20f' % 2.175
dem ausgewertet wird'2.17499999999999982236'
.) In Python 2.7, das eine halbe Aufrundung verwendet, gibt round (2.175) 2.17 zurück, und diese Art von Ergebnis wird als Einschränkung des Gleitkommas dokumentiert .round
(das Round-Half-Up implementiert) nicht gut genug ist. Bitte bearbeiten Sie die Frage, um den tatsächlichen Anwendungsfall anzugeben. Möglicherweise wird dies durch die Vermeidung von Gleitkommazahlen und die Verwendung von Dezimalstellen erfüllt.Ja; Verwenden
Decimal
Sie diese Option, um Ihre Zahlen im gesamten Programm darzustellen, wenn Sie Zahlen wie 2.675 genau darstellen und auf 2.68 statt 2.67 runden müssen.Es geht nicht anders. Die Gleitkommazahl, die auf Ihrem Bildschirm als 2.675 angezeigt wird, ist nicht die reelle Zahl 2.675. in der Tat ist es etwas weniger als 2,675, weshalb es auf 2,67 abgerundet wird:
Es wird nur in Zeichenfolgenform angezeigt,
'2.675'
da dies zufällig die kürzeste Zeichenfolge istfloat(s) == 2.6749999999999998
. Beachten Sie, dass diese längere Darstellung (mit vielen 9s) auch nicht genau ist.Wie auch immer Sie Ihre Rundungsfunktion schreiben, es ist nicht möglich
my_round(2.675, 2)
, auf2.68
und auchmy_round(2 + 0.6749999999999998, 2)
auf aufzurunden2.67
; weil die Eingänge tatsächlich die gleiche Gleitkommazahl haben.Wenn Ihre Nummer 2.675 jemals in einen Float und wieder zurück umgewandelt wird, haben Sie bereits die Information verloren, ob sie auf- oder abgerundet werden soll. Die Lösung besteht nicht darin, es überhaupt schweben zu lassen.
quelle
Nachdem ich sehr lange versucht hatte, eine elegante einzeilige Funktion zu erstellen, bekam ich etwas, das in seiner Größe mit einem Wörterbuch vergleichbar ist.
Ich würde sagen, der einfachste Weg, dies zu tun, ist nur zu
Ich würde anerkennen, dass dies nicht in allen Fällen korrekt ist, aber funktionieren sollte, wenn Sie nur eine einfache, schnelle Problemumgehung wünschen.
quelle
round_half_up(1.499999999, 0)
2.0 anstelle von 1.0 zurück.