So implementieren Sie die Division mit Rundung in Richtung Unendlichkeit in Python

8

Ich möchte 3/2 gleich 2 nicht 1,5

Ich weiß, dass es einen mathematischen Ausdruck für diese Operation gibt (nicht als Aufrunden bezeichnet), aber ich kann mich im Moment nicht daran erinnern. Wie auch immer, wie mache ich das, ohne zwei Funktionen zu machen?

von dem, was ich NICHT will:

%Vor%

ex von was ich will:

%Vor%     
Kbob 24.08.2011, 20:20
quelle

8 Antworten

16

Um eine kurze Antwort zu geben ...

Python bietet nur native Operatoren für zwei Arten von Unterteilungen an: "echte" Division und "abgerundete" Division. Was Sie wollen, ist nicht als einzelne Funktion verfügbar. Es ist jedoch möglich, eine Anzahl verschiedener Typen von Division-mit-Rounding unter Verwendung einiger kurzer Ausdrücke einfach zu implementieren.

Auf Antrag des Titels: Bei streng integer Eingaben kann "Aufrunden" mit (a+(-a%b))//b implementiert werden, und "Runden weg von Null" kann mit der komplexeren a//b if a*b<0 else (a+(-a%b))//b implementiert werden. Einer davon ist wahrscheinlich, was Sie wollen. Warum?

Um eine längere Antwort zu geben ...

Lassen Sie mich zuerst die Unterfrage über warum 3/2==1 und math.ceil(3/2)==1.0 beantworten, um zu erklären, wie der Python-Divisionsoperator funktioniert. Es gibt zwei Hauptprobleme ...

float vs int division: Unter Python 2 verhält sich die Division abhängig vom Typ der Eingaben unterschiedlich. Wenn sowohl a als auch b Integer sind, führt a/b die Division "Abrunden" oder "Floor Integer" durch (zB 3/2==1 , aber -3/2==-2 ). Dies entspricht int(math.floor(float(a)/b)) .

Wenn jedoch mindestens eines von a und b Gleitkommazahlen sind, führt Python eine "wahre" Division durch und gibt Ihnen ein float Ergebnis (zB 3.0/2==1.5 und -3.0/2==-1.5 ). Deshalb sehen Sie manchmal die Konstruktion float(a)/b : Sie wird verwendet, um eine echte Division zu erzwingen, obwohl beide Eingaben ganze Zahlen sind (zB float(3)/2==1.5 ). Deshalb gibt Ihr Beispiel math.ceil(3/2) 1.0 zurück, während math.ceil(float(3)/2) 2.0 zurückgibt. Das Ergebnis wurde bereits abgerundet, bevor es math.ceil() erreicht.

"true division" standardmäßig : Im Jahr 2001 wurde entschieden ( PEP 238 ), dass Pythons Division-Operator so geändert werden sollte, dass er immer "echte" Divisionen ausführt, unabhängig davon, ob die Eingaben Floats oder Integers sind (zB würde dies 3/2==1.5 ergeben). Um bestehende Skripte nicht zu unterbrechen, wurde die Änderung des Standardverhaltens solange zurückgestellt, bis Python 3.0; Um dieses Verhalten unter Python 2.x zu erhalten, müssen Sie es pro Datei aktivieren, indem Sie from __future__ import division am Anfang der Datei hinzufügen. Ansonsten wird das alte typabhängige Verhalten verwendet.

Aber die "Abrundung" wird immer noch häufig gebraucht, so dass die PEP nicht ganz damit klarkommt. Stattdessen wurde ein neuer Divisionsoperator eingeführt: a//b , der immer eine Abrundung durchführt, auch wenn die Eingaben Floats enthalten. Dies kann verwendet werden, ohne etwas besonderes unter Python 2.2+ und 3.x zu tun.

Aus diesem Grund, Division-mit-Rundung:

Um die Dinge zu vereinfachen, benutzen die folgenden Ausdrücke den a//b Operator, wenn sie an Ganzzahlen arbeiten, da sie sich unter allen Python-Versionen gleich verhalten. Ich nehme auch an, dass 0<=a%b<b wenn b positiv ist und b<=a%b<=0 wenn b negativ ist. So verhält sich Python, aber andere Sprachen können leicht unterschiedliche Moduloperatoren haben.

Die vier Grundtypen der Ganzzahldivision mit Rundung:

  • "abrunden" aka "floor integer" aka "rund zu minus infinity" divsion: python bietet dies nativ via a//b an.

  • "Aufrunden" aka "Decke-Ganzzahl" aka "Runde zu positiver Unendlichkeit" -Teilung: Dies kann über int(math.ceil(float(a)/b)) oder (a+(-a%b))//b erreicht werden. Die letztere Gleichung funktioniert, weil -a%b 0 ist, wenn a ein Vielfaches von b ist, und ansonsten die Menge, die wir a hinzufügen müssen, um zum nächsthöheren Vielfachen zu kommen.

  • "Runde gegen Null" aka "abgeschnittene" Division - dies kann über int(float(a)/b) erreicht werden. Dies zu tun, ohne Fließkomma zu verwenden, ist komplizierter ... Da Python nur eine abgerundete Ganzzahldivision bietet und der % -Operator eine ähnliche Abrundungsverzerrung hat, haben wir keine Nicht-Gleitkommaoperatoren, die symmetrisch runden etwa 0. Also kann ich mir nur einen stückweisen Ausdruck aus Abrundung und Zusammenfassung zusammenstellen: a//b if a*b>0 else (a+(-a%b))//b .

  • "Runde weg von Null" aka "Runde zu (entweder) Unendlichkeit" -Teilung - das ist leider noch kniffliger als rund-gegen-Null. Wir können das Abschneideverhalten des int -Operators nicht mehr nutzen, daher kann ich mir auch bei Floating-Point-Operationen keinen einfachen Ausdruck vorstellen. Also muss ich mit der Umkehrung des Rund-zu-Null-Ausdrucks gehen und a//b if a*b<0 else (a+(-a%b))//b verwenden.

Beachten Sie, dass (a+b-1)//b , wenn Sie nur positive Ganzzahlen verwenden, eine noch effizientere Rundung von Null als jede der obigen Lösungen bietet, aber für Negative auseinanderfällt.

Ich hoffe, das hilft ... und freut mich, Änderungen vornehmen zu können, wenn jemand bessere Gleichungen für die Entfernung von Null vorschlagen kann. Ich finde diejenigen, die ich besonders unbefriedigend finde.

    
Eli Collins 24.08.2011 20:36
quelle
5

Integrale Aufteilung in Python 3:

3 // 2 == 1

Nicht-integrale Division in Python 3:

3 / 2 == 1.5

Wovon du sprichst, ist keine Teilung.

    
Andrey Agibalov 24.08.2011 20:23
quelle
4

Die Frage des OP lautet wie folgt: "Wie implementiere ich in Python die Division mit Rundung in Richtung Unendlichkeit?" (schlagen Sie vor, den Titel zu ändern).

Dies ist ein vollkommen legitimer Rundungsmodus gemäß dem IEEE-754 Standard (lesen Sie diese Übersicht ), und der Begriff dafür ist "rund zu Unendlichkeit "(oder" weg von Null "). Die meisten der 9 Downvotes verprügelten die OP unfair. Ja, es gibt keine single-function Methode, dies in nativem Python zu tun, aber wir können round(float(a)/b) oder auch die Unterklasse numbers.Number verwenden und __div__() überschreiben.

Das OP müsste klären, ob -3/2 auf -2 oder -1 runden soll (oder nicht - negative Operanden beachten). Da sie bereits gesagt haben, dass sie keine Runde nach oben wollen, können wir ableiten, dass -3/2 auf -2 runden sollte.

Genug Theorie. Für Implementierungen:

  • Wenn Sie nur die schnelle und einfache Ein-Zeilen-Lösung für die Rundung in Richtung unendlich wünschen, verwenden Sie round(float(a)/b)
  • math.ceil(float(a)/b) gibt Ihnen eine Runde nach oben, die Sie sagten, dass Sie nicht wollen

  • Aber wenn dies Ihre Standard-Divisionsoperation ist, oder Sie tun eine Menge davon, dann mögen Sie den Pseudocode unten: von einer der Unterklassen von numbers.Number Real, Rational oder Integral (neu in 2.6) erben, __div__() neu definieren oder eine nicht standardmäßige alternative __divra__() -Operation definieren. Sie könnten ein Klassenmitglied oder eine Klassenmethode rounding_mode definieren und während der Divisionen nachschlagen. Seien Sie vorsichtig mit __rdiv__() und mischen Sie mit normalen Floats.

.

%Vor%     
smci 24.08.2011 22:19
quelle
2

Wenn Sie zwei ganze Zahlen teilen, ist das Ergebnis eine ganze Zahl.
3 / 2 ist gleich 1 , nicht 1.5 .
Siehe die Dokumentation , Anmerkung 1:

  

Bei (ganzzahligen oder langen) Ganzzahlen ist das Ergebnis eine Ganzzahl. Das Ergebnis wird immer auf minus unendlich gerundet: 1/2 ist 0, (-1) / 2 ist -1, 1 / (- 2) ist -1 und (-1) / (- 2) ist 0. Beachten Sie, dass Das Ergebnis ist eine lange Ganzzahl, wenn einer der Operanden unabhängig vom numerischen Wert eine lange Ganzzahl ist.

Sobald Sie 1 von der Division erhalten haben, können Sie das nicht in 2 umwandeln.

Um 1.5 zu erhalten, benötigen Sie eine Gleitkommadivision: 3.0 / 2 .
Sie können dann math.ceil aufrufen, um 2 zu erhalten.

Sie sind falsch; es gibt keine mathematische Funktion, die sich teilt, dann aufrundet.
Das Beste, was Sie tun können, ist eine eigene Funktion zu schreiben, die zwei Gleitkommazahlen benötigt und math.ceil aufruft.

    
SLaks 24.08.2011 20:23
quelle
1

Was Sie wahrscheinlich wollen, ist etwas wie:

%Vor%

Sie könnten auch einen Import aus der Zukunft machen:

%Vor%

Wenn Sie dies tun, müssen Sie den doppelten Schrägstrich verwenden, um das aktuelle Verhalten der Ganzzahldivision zu erhalten:

%Vor%     
Chris Pickett 24.08.2011 20:27
quelle
1

Ingmp2 ist eine Ganzzahl-Division mit Glättung (bis + Inf), Grundrundung (bis -Inf) und Trunkierung (bis 0).

%Vor%

gmpy2 steht unter Zypern

zur Verfügung

(Haftungsausschluss: Ich bin der aktuelle Betreuer von gmpy und gmpy2.)

    
casevh 24.08.2011 23:33
quelle
1

Ich denke, wonach Sie suchen:

vorausgesetzt, Sie haben x (3) und y (2),

result = (x + y - 1) // y;

Dies entspricht einer Obergrenze ohne die Verwendung von Gleitpunkten.

Natürlich kann y nicht 0 sein.

    
Alexis Wilke 24.08.2011 22:26
quelle
0

Zuerst möchten Sie die Gleitkommadivision in den Argumenten verwenden. Verwenden Sie:

%Vor%

Wenn Sie immer aufrunden möchten, also f(3/2)==2 und f(1.4)==2 , dann möchten Sie, dass f math.trunc(math.ceil(x)) ist.

Wenn Sie die nächste Ganzzahl erhalten möchten, aber die Verbindungen aufgerundet werden, dann möchten Sie math.trunc(x + 0.5) . Auf diese Weise f(3/2)==2 und f(1.4)==1 .

    
Nathan Whitehead 25.08.2011 21:30
quelle