Finden Sie maximal drei Zahlen in C, ohne die bedingte Anweisung und den ternären Operator zu verwenden

Lesezeit: 10 Minuten

Benutzer-Avatar
Microsoft-Entwickler

Ich muss maximal drei vom Benutzer bereitgestellte Nummern finden, jedoch mit einigen Einschränkungen. Es ist nicht erlaubt, eine bedingte Anweisung zu verwenden. Ich habe versucht, einen ternären Operator wie unten zu verwenden.

max=(a>b?a:b)>c?(a>b?a:b):c

Aber auch hier ist es auf die Verwendung des ternären Operators beschränkt. Jetzt bekomme ich keine Ahnung, wie ich das machen soll?

  • Dies fällt definitiv in die Kategorie “Es ist unwahrscheinlich, dass es in Zukunft jemandem hilft” (es sei denn, es handelt sich um einen Schüler eines inkompetenten Erziehers). Verwenden die Werkzeuge, die Sie haben, zeigt diese Art von Frage, dass Ihr Kurs wahrscheinlich Zeitverschwendung ist.

    – paxdiablo

    16. August 2011 um 5:42 Uhr

  • @paxdiablo. Es ist keine Zeitverschwendung. Eher seine gute Herausforderung. Es gibt einige mögliche Wege, die jedoch eingeschränkt sind, also fordern Sie heraus, neue Wege zu finden.

    – Microsoft-Entwickler

    16. August 2011 um 5:50 Uhr

  • @Chirag, eine Herausforderung ohne nützliches Ergebnis ist eine nutzlose Herausforderung. In welchem ​​Codestück der realen Welt würden Sie jemals ein solches Biest verwenden? Sie würden bei einer Codeüberprüfung geteert und gefedert werden. John, wenn Sie jemandem etwas über Kurzschlussoperationen beibringen möchten, gibt es bessere Möglichkeiten, als zweifelhafte Rätsel zu verwenden.

    – paxdiablo

    16. August 2011 um 6:03 Uhr


  • @paxdiablo: Solche Rätsel sind gut für geistige Übungen, besonders wenn du Student bist. 🙂

    – Nawaz

    16. August 2011 um 6:04 Uhr

  • @paxdiablo: Ehrlich gesagt denke ich, dass dies eine Übung in Bitoperationen ist

    – Foo Bah

    16. August 2011 um 6:08 Uhr

Benutzer-Avatar
Nawaz

Kurzschließen in booleschen Ausdrücken ausnutzen:

int max(int a, int b, int c)
{
     int m = a;
     (m < b) && (m = b); //these are not conditional statements.
     (m < c) && (m = c); //these are just boolean expressions.
     return m;
}

Erläuterung:

In Boolesch AND Betrieb wie z x && yy wird ausgewertet dann und nur dann, wenn x ist wahr. Wenn x ist dann falsch y wird nicht ausgewertet, da der gesamte Ausdruck falsch wäre, was auch ohne Auswertung abgeleitet werden kann y. Dies wird als Kurzschließen bezeichnet, wenn der Wert eines booleschen Ausdrucks abgeleitet werden kann, ohne alle darin enthaltenen Operanden auszuwerten.

Wenden Sie dieses Prinzip auf den obigen Code an. Anfänglich m ist a. Wenn jetzt (m < b) stimmt, dann heißt das, b ist größer als m (was eigentlich ist a), also der zweite Unterausdruck (m = b) wird ausgewertet u m ist eingestellt auf b. Wenn doch (m < b) falsch ist, wird der zweite Unterausdruck nicht ausgewertet und m wird bleiben a (was größer ist als b). Auf ähnliche Weise wird der zweite Ausdruck ausgewertet (in der nächsten Zeile).

Kurz gesagt, Sie können den Ausdruck lesen (m < x) && (m = x) wie folgt: einstellen m zu x dann und nur dann, wenn m ist weniger als x dh (m < x) ist wahr. Ich hoffe, das hilft Ihnen, den Code zu verstehen.

Testcode:

int main() {
        printf("%d\n", max(1,2,3));
        printf("%d\n", max(2,3,1));
        printf("%d\n", max(3,1,2));
        return 0;
}

Ausgabe:

3
3
3

Beachten Sie die Implementierung von max gibt Warnungen aus, weil ausgewertete Ausdrücke nicht verwendet werden:

prog.c:6: Warnung: Berechneter Wert wird nicht verwendet
prog.c:7: Warnung: Berechneter Wert wird nicht verwendet

Um diese (harmlosen) Warnungen zu vermeiden, können Sie implementieren max wie:

int max(int a, int b, int c)
{
     int m = a;
     (void)((m < b) && (m = b)); //these are not conditional statements.
     (void)((m < c) && (m = c)); //these are just boolean expressions.
     return m;
}

Der Trick besteht darin, dass wir jetzt die booleschen Ausdrücke in umwandeln voidwodurch die Warnungen unterdrückt werden:

  • Können Sie etwas erklären?

    – Microsoft-Entwickler

    16. August 2011 um 5:46 Uhr

  • Ha. Das ist clever und hinterhältig.

    – GWW

    16. August 2011 um 5:46 Uhr

  • @Chirag Fanse: Er nutzt Kurzschlüsse aus.

    – GWW

    16. August 2011 um 5:47 Uhr

  • Beachten Sie, dass diese Lösung nicht vermeidet Verzweigung, also gibt es zwar keine bedingten Ausdrücke im C++-Code, aber Verzweigungen im echten Code. Das && ist eine implizite if. Wenn das Ziel der Vermeidung if oder der ternäre Operator (?:) war, Verzweigungen zu vermeiden, die die Verarbeitung durch die CPU beschleunigen können, dann sind andere Lösungen besser.

    – David Rodríguez – Dribeas

    25. August 2011 um 7:53 Uhr

  • @Nawaz: Vermeiden von Verzweigungen, was wiederum die Verarbeitung beschleunigen kann, da der Compiler bei einer falschen Vorhersage der Verzweigung einen Teil der Verarbeitungspipeline leeren muss, und das kann sein teuer (durch eine sehr relative Bedeutung von teuer). Normalerweise, wenn Sie vermeiden möchten if man möchte eigentlich nicht vermeiden, die beiden Buchstaben einzutippen, sondern den Zweig, der durch das if in das Programm eingefügt wird. Das heißt, wenn es Sie interessiert, was Sie in den meisten Fällen nicht einmal an solche Mikrooptimierungen auf niedriger Ebene denken sollten.

    – David Rodríguez – Dribeas

    25. August 2011 um 8:38 Uhr


Angenommen, Sie haben es mit ganzen Zahlen zu tun, wie wäre es mit:

#define max(x,y) (x ^ ((x ^ y) & -(x < y)))
int max3(int x, int y, int z) {
    return max(max(x,y),z);
}

Benutzer-Avatar
David Rodríguez – Dribeas

Nur um eine weitere Alternative hinzuzufügen, um die bedingte Ausführung zu vermeiden (die ich nicht verwenden würde, aber in den Lösungen zu fehlen schien):

int max( int a, int b, int c ) {
   int l1[] = { a, b };
   int l2[] = { l1[ a<b ], c };
   return l2[ l2[0] < c ];
}

Der Ansatz nutzt (wie die meisten anderen) die Tatsache, dass das Ergebnis eines booleschen Ausdrucks bei der Konvertierung in int entweder 0 oder 1 ergibt. Die vereinfachte Version für zwei Werte wäre:

int max( int a, int b ) {
   int lookup[] { a, b };
   return lookup[ a < b ];
}

Wenn der Ausdruck a<b ist richtig, wir kehren zurück b, sorgfältig im ersten Index des Lookup-Arrays gespeichert. Wenn der Ausdruck falsch ergibt, kehren wir zurück a das als Element gespeichert wird 0 des Lookup-Arrays. Wenn Sie dies als Baustein verwenden, können Sie sagen:

int max( int a, int b, int c ) {
   int lookup[ max(a,b), c ];
   return lookup[ max(a,b) < c ];
}

Was trivial in den obigen Code umgewandelt werden kann, indem der zweite Aufruf des Inneren vermieden wird max unter Verwendung des bereits gespeicherten Ergebnisses in lookup[0] und Einfügen des ursprünglichen Aufrufs an max(int,int).


(Dieser Teil ist nur ein weiterer Beweis, den Sie messen müssen, bevor Sie zu Schlussfolgerungen kommen, siehe die Bearbeitung am Ende.)

Welche würde ich eigentlich verwenden … nun, wahrscheinlich die von @Foo Baa hier, die so modifiziert wurde, dass sie eine Inline-Funktion anstelle eines Makros verwendet. Die nächste Option wäre entweder diese oder die von @MSN hier.

Der gemeinsame Nenner dieser drei Lösungen, die in der akzeptierten Antwort nicht enthalten sind, besteht darin, dass sie nicht nur das syntaktische Konstrukt von vermeiden if oder der ternäre Operator ?:aber das vermeiden sie Verzweigung insgesamt, und das kann sich auf die Leistung auswirken. Das Zweig-Prädiktor in der CPU kann unmöglich fehlen, wenn es keine Verzweigungen gibt.


Wenn Sie Leistung in Betracht ziehen, messen Sie zuerst und denken Sie dann nach

Ich habe tatsächlich einige der verschiedenen Optionen für ein 2-Wege-Max implementiert und den vom Compiler generierten Code analysiert. Die folgenden drei Lösungen generieren denselben Assemblercode:

int max( int a, int b ) { if ( a < b ) return b; else return a; }
int max( int a, int b ) { return (a < b? b : a ); }
int max( int a, int b ) {
   (void)((a < b) && (a = b));
   return a;
}

Was nicht verwunderlich ist, da alle drei genau die gleiche Operation darstellen. Die interessante Information ist, dass der generierte Code keine Verzweigung enthält. Die Implementierung ist einfach mit der cmovge anleitung (test durchgeführt mit g++ auf einer intel x64 plattform):

movl    %edi, %eax       # move a into the return value
cmpl    %edi, %esi       # compare a and b
cmovge  %esi, %eax       # if (b>a), move b into the return value
ret

Der Trick liegt in der bedingten Bewegungsanweisung, die jede mögliche Verzweigung vermeidet.

Keine der anderen Lösungen hat Verzweigungen, aber alle übersetzen in mehr CPU-Anweisungen als jede dieser Lösungen, was uns am Ende des Tages versichert, dass wir es immer tun sollten einfachen Code schreiben und lassen Sie den Compiler es für uns optimieren.

  • cmovge ist eine Verzweigungsanweisung. Es sieht das Ergebnis einer vorherigen Bedingung aus und führt eine entsprechende Aktion aus. Es ist zufällig eine ziemlich gute bedingte Anweisung dafür, aber es ist immer noch eine Verzweigung.

    – Flexo

    11. November 2011 um 19:07 Uhr

  • @awoodland: Ich glaube nicht, dass man das a nennen kann Zweig, kann der Codefluss unmöglich in zwei Richtungen gehen. Es berücksichtigt zwar den vorherigen Wert, um das eine oder andere Ergebnis zu erzielen, aber es kann nicht sein falsch vorhergesagtes muss die Befehlspipeline nicht leeren … Es wird wahrscheinlich langsamer sein als andere Befehle (es kann nicht neu geordnet werden, die Ausführung hängt von der vorherigen Ausführung ab, also kann es nicht damit parallelisiert werden …), aber das ist nicht der Fall Zweiges sei denn, ich übersehe hier etwas.

    – David Rodríguez – Dribeas

    11. November 2011 um 22:19 Uhr


  • Sein Verhalten hängt vom Bedingungsregister ab, von dem ich gesagt hätte, dass es die eigentliche Definition einer Verzweigung ist. Logischerweise sieht sein Verhalten wie eine Verzweigung aus – die Werte im Speicher können eines von zwei Dingen werden, nachdem die Anweisung abgeschlossen ist. Wenn das vorhergesagt und folglich falsch vorhergesagt werden kann, bin ich mir bei aktuellen Prozessordesigns weniger sicher. Es ist nicht unvorstellbar, dass eine Vorhersage des “nichts tun”-Pfads fälschlicherweise gemacht werden könnte, und eine Vorhersage/Fehlvorhersage ist sowieso keine Voraussetzung dafür, dass eine Anweisung eine Verzweigung ist. (Ich stimme zu, dass es die Frage beantwortet – es ist keine bedingte Aussage)

    – Flexo

    11. November 2011 um 22:30 Uhr

  • @awoodland: Ich habe ein anderes Verständnis davon, was a Zweig ist, aber ich bin kein Assembler-Experte. Für mich ist ein Punkt im Code, an dem die nächste Anweisung (Anmerkung: nicht das Ergebnis der Anweisung, sondern die nächste Anweisung) eine von einer Menge sein kann (dh ein bedingter Sprung), und das damit verbundene Problem ist, dass die Prozessor-Anweisungspipeline im Falle einer Fehlvorhersage möglicherweise fallen gelassen werden. In diesem Fall ist der nächste Befehl immer bekannt, ob der Registerwert geändert wird oder nicht, und ob der Wert aktualisiert wird oder nicht, die Befehlspipeline kann optimal verwendet werden.

    – David Rodríguez – Dribeas

    12. November 2011 um 19:55 Uhr

Benutzer-Avatar
Keith Thompson

AKTUALISIEREN: Wenn ich mir das 4 Jahre später anschaue, sehe ich, dass es schlecht versagt, wenn zwei oder mehr der Werte zufällig gleich sind. Ersetzen > durch >= ändert das Verhalten, behebt aber nicht das Problem. Es ist möglicherweise noch zu retten, daher werde ich es noch nicht löschen, aber verwenden Sie es nicht im Produktionscode.


Okay, hier ist meins:

int max3(int a, int b, int c)
{
    return a * (a > b & a > c) +
           b * (b > a & b > c) +
           c * (c > a & c > b);
}

Beachten Sie, dass die Verwendung von & statt && vermeidet jeden bedingten Code; es beruht darauf, dass > ergibt immer 0 oder 1. (Der generierte Code für a > b kann bedingte Sprünge beinhalten, aber sie sind von C aus nicht sichtbar.)

int fast_int_max(int a, int b)
{
    int select= -(a < b);
    unsigned int b_mask= select, a_mask= ~b_mask;

    return (a&a_mask)|(b&b_mask);
}

int fast_int_max3(int a, int b, int c)
{
    return fast_int_max(a, fast_int_max(b, c));
}

Benutzer-Avatar
gsk

Boolesche Operatoren (einschließlich <, && usw.) werden normalerweise in bedingte Operationen auf Maschinencodeebene übersetzt, erfüllen Sie also nicht den Geist der Herausforderung. Hier ist eine Lösung, die jeder vernünftige Compiler nur in arithmetische Anweisungen ohne bedingte Sprünge übersetzen würde (vorausgesetzt, long hat mehr Bits als int und long ist 64 Bits). Die Idee ist, dass "m" das Vorzeichenbit von b - a erfasst und repliziert, sodass m entweder alle 1-Bits (wenn a > b) oder alle Null-Bits (wenn a <= b) ist. Beachten Sie, dass long verwendet wird, um einen Überlauf zu vermeiden. Wenn Sie aus irgendeinem Grund wissen, dass b - a nicht über-/unterläuft, dann ist die Verwendung von long nicht erforderlich.

int max(int a, int b)
{
    long d = (long)b - (long)a;
    int m = (int)(d >> 63);
    return a & m | b & ~m;
}

int max(int a, int b, int c)
{
    long d;
    int m;
    d = (long)b - (long)a;
    m = (int)(d >> 63);
    a = a & m | b & ~m;
    d = (long)c - (long)a;
    m = (int)(d >> 63);
    return a & m | c & ~m;
}

Benutzer-Avatar
Lee Louvière

Keine Bedingungen. Nur eine Besetzung zu uint. Perfekte Lösung.

int abs (a) { return (int)((unsigned int)a); }
int max (a, b) { return (a + b + abs(a - b)) / 2; }
int min (a, b) { return (a + b - abs(a - b)) / 2; }


void sort (int & a, int & b, int & c)
{
   int max = max(max(a,b), c);
   int min = min(min(a,b), c);
   int middle = middle = a + b + c - max - min;
   a = max;
   b = middle;
   c = min;
}

  • 1) sort (int & a, int & b, int & c) ist keine gültige C-Syntax. 2) a-b Überläufe, die zu UB in C führen, für etwa die Hälfte aller a,b Kombinationen.

    – chux – Wiedereinsetzung von Monica

    14. September 2015 um 17:51 Uhr

  • @chux Von oben würde ich argumentieren, dass es nur ein Viertel aller Kombinationen ist. Gehen von 0 (kein Überlauf möglich) bis MAX_INT (die Hälfte aller Kombinationen verursachen einen Überlauf). Das Ergebnis sollte in der Mitte liegen. Gleiches gilt für 0 bis MIN_INT

    – Kami Kaze

    16. November 2018 um 10:40 Uhr


1375610cookie-checkFinden Sie maximal drei Zahlen in C, ohne die bedingte Anweisung und den ternären Operator zu verwenden

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy