Rundungsverhalten von Python 3.x.


176

Ich habe gerade neu gelesen, was in Python 3.0 neu ist und es heißt:

Die Rundungsstrategie der Funktion round () und der Rückgabetyp haben sich geändert. Genaue Fälle auf halber Strecke werden jetzt auf das nächste gerade Ergebnis gerundet, anstatt von Null weg. (Zum Beispiel gibt Runde (2.5) jetzt 2 statt 3 zurück.)

und die Dokumentation für Runde :

Für die eingebauten Typen, die round () unterstützen, werden die Werte auf das nächste Vielfache von 10 zur Potenz minus n gerundet. Wenn zwei Vielfache gleich nahe beieinander liegen, wird auf die gerade Wahl gerundet

Also unter v2.7.3 :

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

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

wie ich erwartet hätte. Jetzt jedoch unter v3.2.3 :

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

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

Dies scheint nicht intuitiv zu sein und widerspricht dem, was ich über Rundungen verstehe (und zwangsläufig Menschen stolpern lässt). Englisch ist nicht meine Muttersprache, aber bis ich dies las, dachte ich, ich wüsste, was Rundung bedeutet: - / Ich bin mir sicher, dass es zum Zeitpunkt der Einführung von v3 einige Diskussionen darüber gegeben haben muss, aber ich konnte keinen guten Grund dafür finden meine Suche.

  1. Hat jemand einen Einblick, warum dies geändert wurde?
  2. Gibt es andere gängige Programmiersprachen (z. B. C, C ++, Java, Perl usw.), die diese Art von (für mich inkonsistenter) Rundung durchführen?

Was fehlt mir hier?

UPDATE: @ Li-aungYips Kommentar zu "Banker-Rundung" gab mir den richtigen Suchbegriff / die richtigen Suchbegriffe und ich fand diese SO-Frage: Warum verwendet .NET die Banker-Rundung als Standard? , also werde ich das sorgfältig lesen.


27
Ich habe keine Zeit, dies nachzuschlagen, aber ich glaube, das nennt man "Banker-Rundung". Ich glaube, dass es in der Finanzbranche üblich ist.
Li-aung Yip

2
@sberry gut, ja, sein Verhalten stimmt mit seiner eigenen Beschreibung überein. Wenn es also sagen würde, dass "Rundung" seinen Wert verdoppelt und es getan hat, wäre es auch konsistent :) .. aber es scheint im Widerspruch zu dem zu stehen, was Rundung üblicherweise bedeutet . Also suche ich ein besseres Verständnis.
Levon

1
@ Li-aungYip Danke für den Hinweis zu "Banker's Rounding". Ich werde es nachschlagen.
Levon


3
Nur eine Anmerkung: Banker-Rundungen sind nicht nur im Finanzbereich üblich. So wurde mir schon in den
70ern

Antworten:


160

Der Weg von Python 3.0 wird heutzutage als Standardrundungsmethode angesehen, obwohl einige Sprachimplementierungen noch nicht im Bus sind.

Die einfache Technik "Immer 0,5 aufrunden" führt zu einer leichten Tendenz zur höheren Zahl. Bei einer großen Anzahl von Berechnungen kann dies von Bedeutung sein. Der Python 3.0-Ansatz beseitigt dieses Problem.

Es gibt mehr als eine gängige Rundungsmethode. IEEE 754, der internationale Standard für Gleitkomma-Mathematik, definiert fünf verschiedene Rundungsmethoden (die von Python 3.0 verwendete ist die Standardeinstellung). Und da sind noch andere.

Dieses Verhalten ist nicht so bekannt, wie es sein sollte. AppleScript war, wenn ich mich richtig erinnere, ein früher Anwender dieser Rundungsmethode. Der roundBefehl in AppleScript bietet tatsächlich mehrere Optionen, aber Round-to-Even ist die Standardeinstellung wie in IEEE 754. Anscheinend hatte der Ingenieur, der den roundBefehl implementiert hat, alle Anfragen satt, "ihn so zu machen, wie ich es gelernt habe Schule ", die er genau das implementiert hat: round 2.5 rounding as taught in schoolist ein gültiger AppleScript-Befehl. :-)


4
Ich wusste nicht, dass diese "Standard-Rundungsmethode heutzutage ziemlich universell" ist. Würden Sie (oder jemand anderes) wissen, ob C / C ++ / Java / Perl oder andere "Mainstream" -Sprachen die Rundung auf die gleiche Weise implementieren?
Levon

3
Ruby macht es. Die .NET-Sprachen von Microsoft tun dies. Java scheint jedoch nicht zu sein. Ich kann es nicht für jede mögliche Sprache finden, aber ich denke, es ist am häufigsten in relativ neu gestalteten Sprachen. Ich stelle mir vor, C und C ++ sind alt genug, dass sie es nicht tun.
Kindall

5
Ruby kehrt zurück 3für2.5.round
jfs

14
Ich habe ein wenig über den Umgang von AppleScript hinzugefügt, weil ich die sarkastische Art und Weise liebe, wie das "alte" Verhalten implementiert wird.
Kindall

2
@kindall Diese Methode ist seit 1985 (als IEEE 754-1985 veröffentlicht wurde) der IEEE-Standardrundungsmodus. Es wurde auch der Standardwert , da Modus in C Abrunden mindestens C89 (und damit auch in C ++), jedoch , da C99 (und C ++ 11 mit sporadischer Unterstützung davor) einem „round ()“ Funktion wird zur Verfügung , dass Verwendungen Krawatten runden stattdessen von Null ab. Die interne Gleitkomma-Rundung und die Funktionsfamilie rint () entsprechen weiterhin der Einstellung für den Rundungsmodus, die standardmäßig auf gerade Gleichungen rundet.
Wlerin

41

Sie können erhalten Sie die Rundungssteuerung in Py3000 die mit Dezimal - Modul :

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

Danke .. Ich war mit diesem Modul nicht vertraut. Irgendeine Idee, wie ich das Verhalten von Python v 2.x bekommen würde? Die Beispiele, die Sie zeigen, scheinen das nicht zu tun. Nur neugierig, ob das möglich wäre.
Levon

1
@Levon: Die Konstante ROUND_HALF_UPentspricht dem alten Verhalten von Python 2.X.
Morgengrauen

2
Sie können auch einen Kontext für das Dezimalmodul festlegen, der dies implizit für Sie erledigt. Siehe die setcontext()Funktion.
Kindall

Genau das habe ich heute gesucht. Arbeiten wie erwartet in Python 3.4.3. Erwähnenswert ist auch, dass Sie steuern können, wie viel gerundet wird, indem Sie auf wechseln quantize(decimal.Decimal('1'), quantize(decimal.Decimal('0.00')wenn Sie auf die nächsten 100 runden möchten, z. B. für Geld.
Igor

Diese Lösung funktioniert so round(number, ndigits)lange als Ersatz, wie ndigitses positiv ist, aber ärgerlicherweise können Sie sie nicht verwenden, um so etwas zu ersetzen round(5, -1).
Pekka Klärck

15

Nur um hier einen wichtigen Hinweis aus der Dokumentation hinzuzufügen:

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

Hinweis

Das Verhalten von round () für Floats kann überraschend sein: Beispielsweise ergibt round (2.675, 2) 2,67 anstelle der erwarteten 2,68. Dies ist kein Fehler: Es ist das Ergebnis der Tatsache, dass die meisten Dezimalbrüche nicht genau als Float dargestellt werden können. Weitere Informationen finden Sie unter Gleitkomma-Arithmetik: Probleme und Einschränkungen.

Seien Sie also nicht überrascht, wenn Sie in Python 3.2 folgende Ergebnisse erzielen:

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

Das habe ich gesehen. Und meine erste Reaktion: Wer verwendet eine 16-Bit-CPU, die nicht alle Permutationen von "2.67x" darstellen kann? Zu sagen, dass Brüche nicht in float ausgedrückt werden können, scheint hier ein Sündenbock zu sein: Keine moderne CPU ist so ungenau, in JEDER Sprache (außer Python?)
Adam

9
@ Adam: Ich denke du hast ein Missverständnis. Das Binärformat (IEEE 754 binary64), das zum Speichern von Floats verwendet wird, kann nicht 2.675genau dargestellt werden: Der Computer kann am nächsten kommen 2.67499999999999982236431605997495353221893310546875. Das ist ziemlich nah dran, aber es ist nicht genau gleich 2.675: es ist etwas näher 2.67als 2.68. Die roundFunktion macht also das Richtige und rundet sie auf den näheren zweistelligen Wert nach dem Punkt, nämlich 2.67. Dies hat nichts mit Python zu tun und alles mit binärem Gleitkomma.
Mark Dickinson

3
Es ist nicht "das Richtige", weil es eine Quellcode-Konstante erhalten hat :), aber ich verstehe Ihren Standpunkt.
Adam

@Adam: Ich bin in JS auf dieselbe Eigenart gestoßen, daher ist sie nicht sprachspezifisch.
Igor

5

Ich hatte kürzlich auch Probleme damit. Daher habe ich ein Python 3-Modul entwickelt, das zwei Funktionen hat: trueround () und trueround_precision (), die dies ansprechen und dasselbe Rundungsverhalten aufweisen, wie es von der Grundschule gewohnt ist (nicht die Rundung von Bankern). Hier ist das Modul. Speichern Sie einfach den Code und kopieren Sie ihn oder importieren Sie ihn. Hinweis: Das Modul trueround_precision kann das Rundungsverhalten je nach Bedarf gemäß den Flags ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_UP und ROUND_05UP ändern. Informationen zu den folgenden Funktionen finden Sie in den Dokumentzeichenfolgen oder verwenden Sie Hilfe (trueround) und Hilfe (trueround_precision), wenn Sie sie zur weiteren Dokumentation in einen Interpreter kopieren.

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

Hoffe das hilft,

Narnie


5

Python 3.x rundet .5-Werte auf einen geraden Nachbarn

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

Man kann jedoch die Dezimalrundung "zurück" ändern, um bei Bedarf immer .5 aufzurunden:

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

Rundungsverhalten von Python 2 in Python 3.

Hinzufügen von 1 an der 15. Dezimalstelle. Genauigkeit bis zu 15 Stellen.

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

3
Können Sie die Intuition hinter dieser Formel erklären?
Hadi

2
Soweit ich weiß, haben Brüche, die nicht genau dargestellt werden können, bis zu 15 Neuner, dann die Ungenauigkeit. Zum Beispiel 2.675ist 2.67499999999999982236431605997495353221893310546875. Durch Hinzufügen von 1e-15 wird es über 2,675 gekippt und korrekt gerundet. Wenn der Bruch bereits über der Codekonstante liegt, ändert das Hinzufügen von 1e-15 nichts an der Rundung.
Benoit Dufresne

1

Manche Fälle:

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

Zur Behebung:

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

Wenn Sie mehr Dezimalstellen möchten, z. B. 4, sollten Sie (+ 0,0000001) hinzufügen.

Arbeite für mich.


Dies war die einzige Lösung, die für mich funktioniert hat, danke für die Veröffentlichung. Jeder scheint darauf bedacht zu sein, 0,5 auf- und abzurunden, daher konnte ich keine Probleme mit Rundungen mit mehreren Dezimalstellen bewältigen.
Gayathri

-1

Probenreproduktion:

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

Zustände:

Geben Sie die Zahl nach dem Dezimalpunkt auf ndigits gerundet zurück. Wenn ndigits weggelassen wird oder None ist, wird die nächste Ganzzahl an die Eingabe zurückgegeben.

Für die integrierten Typen, die round () unterstützen, werden die Werte auf das nächste Vielfache von 10 gerundet, das der Potenz minus ndigits am nächsten kommt. Wenn zwei Vielfache gleich nahe beieinander liegen, erfolgt die Rundung in Richtung der geraden Wahl (so sind beispielsweise sowohl Runde (0,5) als auch Runde (-0,5) 0 und Runde (1,5) 2). Jeder ganzzahlige Wert gilt für Ziffern (positiv, null oder negativ). Der Rückgabewert ist eine Ganzzahl, wenn ndigits weggelassen wird oder None. Andernfalls hat der Rückgabewert den gleichen Typ wie number.

Runden Sie für eine allgemeine Python-Objektnummer die Delegaten auf Nummer. rund .

Hinweis Das Verhalten von round () für Floats kann überraschend sein: Beispielsweise ergibt round (2.675, 2) 2,67 anstelle der erwarteten 2,68. Dies ist kein Fehler: Es ist das Ergebnis der Tatsache, dass die meisten Dezimalbrüche nicht genau als Float dargestellt werden können. Weitere Informationen finden Sie unter Gleitkomma-Arithmetik: Probleme und Einschränkungen.

Angesichts dieser Einsicht können Sie etwas Mathematik verwenden, um es zu lösen

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

Jetzt können Sie denselben Test mit my_round anstelle von round ausführen.

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

Der einfachste Weg, in Python 3.x zu runden, wie es in der Schule gelehrt wird, ist die Verwendung einer Hilfsvariablen:

n = 0.1 
round(2.5 + n)

Und dies sind die Ergebnisse der Serien 2.0 bis 3.0 (in Schritten von 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

Sie können die Rundung mit dem Modul math.ceil steuern:

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

Das gibt immer die Zahl ohne Dezimalteil zurück, dies ist keine Rundung. Ceil (2,5) = 2, Ceil (2,99) = 2
Krafter

1
Wenn das Zahlenargument in Python3 + eine positive oder negative Zahl ist, gibt die Ceil-Funktion den Ceil-Wert zurück.
Eds_k

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

Ja, es tut mir leid, dass ich mich geirrt habe. Ceil () gibt den Deckenwert zurück, während floor () den Wert zurückgibt, über den ich gesprochen habe. Meiner Meinung nach ist dies jedoch nicht ganz das Rundungsverhalten (beide Funktionen)
nach dem

-4

Versuchen Sie diesen Code:

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

Das Ergebnis wird sein:

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

Die Ausgabe können Sie hier überprüfen: https://i.stack.imgur.com/QQUkS.png

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.