Schnelle Obergrenze einer Ganzzahldivision in C / C ++

262

Gegebene ganzzahlige Werte x und geben sowohl yC als auch C ++ als Quotient q = x/yden Boden des Gleitkommaäquivalents zurück. Ich bin an einer Methode interessiert, stattdessen die Decke zurückzugeben. Zum Beispiel ceil(10/5)=2und ceil(11/5)=3.

Der offensichtliche Ansatz beinhaltet etwas wie:

q = x / y;
if (q * y < x) ++q;

Dies erfordert einen zusätzlichen Vergleich und eine zusätzliche Multiplikation. und andere Methoden, die ich gesehen habe (tatsächlich verwendet), beinhalten das Casting als floatoderdouble . Gibt es eine direktere Methode, die die zusätzliche Multiplikation (oder eine zweite Division) und Verzweigung vermeidet und die auch das Casting als Gleitkommazahl vermeidet?

und und
quelle
70
Die Divide-Anweisung gibt oft gleichzeitig sowohl den Quotienten als auch den Rest zurück, sodass keine Multiplikation erforderlich q = x/y + (x % y != 0);ist. Es reicht einfach aus
phuclv
2
@ LưuVĩnhPhúc dieser Kommentar sollte die akzeptierte Antwort sein, imo.
Andreas Grapentin
1
@ LưuVĩnhPhúc Im Ernst, Sie müssen das als Antwort hinzufügen. Ich habe das nur für meine Antwort während eines Codilitätstests verwendet. Es hat wie ein Zauber funktioniert, obwohl ich nicht sicher bin, wie der Mod-Teil der Antwort funktioniert, aber es hat den Job gemacht.
Zachary Kraus
2
@AndreasGrapentin Die Antwort unten von Miguel Figueiredo wurde fast ein Jahr eingereicht, bevor Lưu Vĩnh Phúc den obigen Kommentar hinterlassen hat. Obwohl ich verstehe, wie ansprechend und elegant Miguels Lösung ist, bin ich nicht geneigt, die akzeptierte Antwort zu diesem späten Zeitpunkt zu ändern. Beide Ansätze bleiben stichhaltig. Wenn Sie sich stark genug dafür fühlen, schlage ich vor, dass Sie Ihre Unterstützung zeigen, indem Sie die Antwort von Miguel unten abstimmen.
undund
1
Seltsamerweise habe ich keine vernünftige Messung oder Analyse der vorgeschlagenen Lösungen gesehen. Sie sprechen von Geschwindigkeit in der Nähe des Knochens, aber es gibt keine Diskussion über Architekturen, Pipelines, Verzweigungsanweisungen und Taktzyklen.
Rado

Antworten:

394

Für positive Zahlen

unsigned int x, y, q;

Aufrunden ...

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

oder (Vermeidung eines Überlaufs in x + y)

q = 1 + ((x - 1) / y); // if x != 0
Sparky
quelle
6
@bitc: Für negative Zahlen gibt C99 meiner Meinung nach eine Rundung auf Null an, ebenso x/ywie die Obergrenze der Division. C90 hat nicht angegeben, wie gerundet werden soll, und ich glaube auch nicht, dass der aktuelle C ++ - Standard dies tut.
David Thornley
6
Siehe Eric Lipperts Beitrag: stackoverflow.com/questions/921180/c-round-up/926806#926806
Mashmagar
3
Hinweis: Dies kann überlaufen. q = ((lang lang) x + y - 1) / y wird nicht. Mein Code ist jedoch langsamer. Wenn Sie also wissen, dass Ihre Zahlen nicht überlaufen, sollten Sie die Version von Sparky verwenden.
Jørgen Fogh
1
@bitc: Ich glaube, Davids Punkt war, dass Sie die obige Berechnung nicht verwenden würden, wenn das Ergebnis negativ ist - Sie würden nur verwendenq = x / y;
Café
12
Der zweite hat ein Problem, bei dem x 0 ist. Ceil (0 / y) = 0, aber es gibt 1 zurück.
Omry Yadan
78

Für positive Zahlen:

    q = x/y + (x % y != 0);
Miguel Figueiredo
quelle
5
Die Divisionsanweisung der gängigsten Architektur enthält auch den Rest in ihrem Ergebnis, so dass dies wirklich nur eine Division benötigt und sehr schnell wäre
phuclv
58

Die Antwort von Sparky ist ein Standardweg, um dieses Problem zu lösen, aber wie ich auch in meinem Kommentar geschrieben habe, laufen Sie Gefahr, überzulaufen. Dies kann durch Verwendung eines breiteren Typs gelöst werden, aber was ist, wenn Sie teilen möchten?long long s ?

Die Antwort von Nathan Ernst bietet eine Lösung, beinhaltet jedoch einen Funktionsaufruf, eine Variablendeklaration und eine Bedingung, wodurch sie nicht kürzer als der OP-Code und wahrscheinlich sogar langsamer ist, da die Optimierung schwieriger ist.

Meine Lösung lautet:

q = (x % y) ? x / y + 1 : x / y;

Es ist etwas schneller als der OPs-Code, da das Modulo und die Division mit derselben Anweisung auf dem Prozessor ausgeführt werden, da der Compiler erkennen kann, dass sie äquivalent sind. Mindestens gcc 4.4.1 führt diese Optimierung mit dem Flag -O2 auf x86 durch.

Theoretisch könnte der Compiler den Funktionsaufruf in Nathan Ernsts Code einbinden und dasselbe ausgeben, aber gcc hat das nicht getan, als ich ihn getestet habe. Dies könnte daran liegen, dass der kompilierte Code an eine einzelne Version der Standardbibliothek gebunden wird.

Abschließend ist nichts davon auf einem modernen Computer von Bedeutung, es sei denn, Sie befinden sich in einer extrem engen Schleife und alle Ihre Daten befinden sich in Registern oder im L1-Cache. Andernfalls sind alle diese Lösungen gleich schnell, mit Ausnahme von Nathan Ernsts, der möglicherweise erheblich langsamer ist, wenn die Funktion aus dem Hauptspeicher abgerufen werden muss.

Jørgen Fogh
quelle
3
Es gab eine einfachere Möglichkeit, den Überlauf zu beheben, einfach das Jahr zu reduzieren:q = (x > 0)? 1 + (x - 1)/y: (x / y);
Ben Voigt
-1: Dies ist ein ineffizienter Weg, da ein billiger * gegen einen teuren% eingetauscht wird. schlimmer als der OP-Ansatz.
Yves Daoust
2
Nein, tut es nicht. Wie ich in der Antwort erklärt habe, ist der Operator% frei, wenn Sie die Division bereits durchführen.
Jørgen Fogh
1
Dann q = x / y + (x % y > 0);ist einfacher als ? :Ausdruck?
Han
Es kommt darauf an, was Sie unter "einfacher" verstehen. Es kann schneller sein oder auch nicht, je nachdem, wie der Compiler es übersetzt. Meine Vermutung wäre langsamer, aber ich müsste sie messen, um sicherzugehen.
Jørgen Fogh
18

Sie können die divFunktion in cstdlib verwenden, um den Quotienten und den Rest in einem einzigen Aufruf abzurufen und dann die Obergrenze wie unten beschrieben separat zu behandeln

#include <cstdlib>
#include <iostream>

int div_ceil(int numerator, int denominator)
{
        std::div_t res = std::div(numerator, denominator);
        return res.rem ? (res.quot + 1) : res.quot;
}

int main(int, const char**)
{
        std::cout << "10 / 5 = " << div_ceil(10, 5) << std::endl;
        std::cout << "11 / 5 = " << div_ceil(11, 5) << std::endl;

        return 0;
}
Nathan Ernst
quelle
12
Als interessanter Fall des Doppelknalls könnten Sie auch return res.quot + !!res.rem;:)
Sam Harwell
Bewirbt ldiv die Argumente nicht immer in lange lange? Und kostet das nichts, Up- oder Downcasting?
Einpoklum
12

Wie wäre es damit? (erfordert y nicht negativ, verwenden Sie dies also nicht in dem seltenen Fall, in dem y eine Variable ohne Nicht-Negativitätsgarantie ist)

q = (x > 0)? 1 + (x - 1)/y: (x / y);

Ich reduzierte mich auf y/yeins, eliminierte den Begriff x + y - 1und damit jede Möglichkeit eines Überlaufs.

Ich vermeide es, x - 1herumzuwickeln, wenn xes sich um einen vorzeichenlosen Typ handelt, der Null enthält.

Bei Vorzeichen xwerden Negativ und Null immer noch zu einem einzigen Fall zusammengefasst.

Wahrscheinlich kein großer Vorteil für eine moderne Allzweck-CPU, aber dies wäre in einem eingebetteten System weitaus schneller als jede andere richtige Antwort.

Ben Voigt
quelle
Ihr else gibt immer 0 zurück, Sie müssen nichts berechnen.
Ruud Althuizen
@ Ruud: nicht wahr. Betrachten Sie x = -45 und y = 4
Ben Voigt
7

Es gibt eine Lösung für positiv und negativ, xaber nur für positiv ymit nur 1 Division und ohne Zweige:

int ceil(int x, int y) {
    return x / y + (x % y > 0);
}

Beachten Sie, wenn xpositiv ist , ist die Division in Richtung Null, und wir sollten 1 hinzufügen, wenn die Erinnerung nicht Null ist.

Wenn xnegativ ist, dann ist die Division gegen Null, das ist es, was wir brauchen, und wir werden nichts hinzufügen, weil x % yes nicht positiv ist

RiaD
quelle
interessant, weil es häufige Fälle gibt, in denen y konstant ist
Wolf
1
Mod erfordert Division, daher ist es hier nicht nur eine Division, sondern vielleicht kann Complier zwei ähnliche Divisionen in eine optimieren.
M. Kazem Akhgary 4.
4

Dies funktioniert für positive oder negative Zahlen:

q = x / y + ((x % y != 0) ? !((x > 0) ^ (y > 0)) : 0);

Wenn es einen Rest gibt, prüft, ob xund yhaben das gleiche Vorzeichen und fügt 1entsprechend hinzu .

Mark Conway
quelle
3

Ich hätte es lieber kommentiert, aber ich habe nicht genügend Wiederholungen.

Soweit mir bekannt ist, ist dies für positive Argumente und einen Divisor mit einer Potenz von 2 der schnellste Weg (getestet in CUDA):

//example y=8
q = (x >> 3) + !!(x & 7);

Nur für generische positive Argumente mache ich das eher so:

q = x/y + !!(x % y);
Anroca
quelle
Es wäre interessant zu sehen, wie q = x/y + !!(x % y);sich q = x/y + (x % y == 0);die q = (x + y - 1) / y;Lösungen in der heutigen CUDA behaupten und welche Leistung sie erbringen.
Greg Kramida
-2

Mit O3 kompilieren. Der Compiler führt die Optimierung gut durch.

q = x / y;
if (x % y)  ++q;
dhb
quelle