21 Stimmen

Würden Sie num%2 oder num&1 verwenden, um zu prüfen, ob eine Zahl gerade ist?

Nun, es gibt mindestens zwei einfache Möglichkeiten, um festzustellen, ob eine bestimmte Zahl gerade ist oder nicht:

 1. if (num%2 == 0) { /* even */ } 
 2. if ((num&1) == 0) { /* even */ }

Ich halte die zweite Option für weitaus eleganter und sinnvoller und verwende sie daher in der Regel. Aber es ist nicht nur eine Frage des Geschmacks; die tatsächliche Leistung kann variieren: Normalerweise sind die bitweisen Operationen (wie die logial-and hier) viel effizienter als eine mod (oder div) Operation. Natürlich kann man argumentieren, dass einige Compiler dies ohnehin optimieren können, und ich stimme dem zu... aber einige werden es nicht tun.

Ein weiterer Punkt ist, dass der zweite Punkt für weniger erfahrene Programmierer etwas schwieriger zu verstehen sein könnte. Darauf würde ich antworten, dass es wahrscheinlich nur für alle von Vorteil ist, wenn diese Programmierer sich die kurze Zeit nehmen, um diese Art von Anweisungen zu verstehen.

Was meinen Sie dazu?

Die beiden angegebenen Ausschnitte sind nur korrekt, wenn num ist entweder ein vorzeichenloser int oder eine negative Zahl mit Zweierkomplement-Darstellung. - Wie einige Kommentare richtig feststellen.

76voto

jason Punkte 227577

Ich programmiere in erster Linie für die Lesbarkeit, daher ist meine Wahl hier num % 2 == 0 . Dies ist viel deutlicher als num & 1 == 0 . Ich überlasse es dem Compiler, für mich zu optimieren, und nehme nur dann Anpassungen vor, wenn das Profiling zeigt, dass dies ein Engpass ist. Alles andere wäre verfrüht.

Ich halte die zweite Option für viel eleganter und sinnvoller

Dem kann ich nicht zustimmen. Eine Zahl ist gerade, weil ihre Kongruenz modulo zwei Null ist, nicht weil ihre binäre Darstellung mit einem bestimmten Bit endet. Binäre Darstellungen sind ein Detail der Implementierung. Sich auf Implementierungsdetails zu verlassen, ist im Allgemeinen ein Codegeruch. Wie bereits von anderen erwähnt, schlägt das Testen des LSB auf Maschinen, die Einerkomplement-Darstellungen verwenden, fehl.

Ein weiterer Punkt ist, dass der zweite Punkt für weniger erfahrene Programmierer etwas schwieriger zu verstehen sein könnte. Darauf würde ich antworten, dass es wahrscheinlich nur für alle von Vorteil ist, wenn diese Programmierer sich die kurze Zeit nehmen, um diese Art von Anweisungen zu verstehen.

Da bin ich anderer Meinung. Wir sollten alle kodieren, um unsere Absicht deutlicher zu machen. Wenn wir auf Gleichmäßigkeit testen, sollte der Code dies ausdrücken (und ein Kommentar sollte unnötig sein). Auch hier drückt die Prüfung der Kongruenz modulo zwei die Absicht des Codes deutlicher aus als die Prüfung des LSB.

Und, was noch wichtiger ist, die Details sollten in einem Ordner versteckt werden. isEven Methode. Wir sollten also sehen if(isEven(someNumber)) { // details } und sehen nur num % 2 == 0 einmal in der Definition von isEven .

24voto

Steve Jessop Punkte 264569

Wenn Sie sagen wollen, dass einige Compiler nicht optimieren %2 dann sollten Sie auch beachten, dass einige Compiler eine Einerkomplement-Darstellung für vorzeichenbehaftete ganze Zahlen verwenden. In dieser Darstellung, &1 gibt die falsche Antwort für negative Zahlen.

Was wollen Sie also - Code, der auf "einigen Compilern" langsam ist, oder Code, der auf "einigen Compilern" falsch ist? Nicht unbedingt die dieselbe Compiler in jedem Fall, aber beide Arten sind extrem selten.

Natürlich, wenn num ein Typ ohne Vorzeichen oder einer der C99-Ganzzahltypen mit fester Breite ist ( int8_t und so weiter, die im 2er-Komplement sein müssen), dann ist das kein Problem. In diesem Fall betrachte ich %2 eleganter und aussagekräftiger zu sein, und &1 für einen Hack halten, der möglicherweise manchmal für die Leistung notwendig sein könnte. Ich denke zum Beispiel, dass CPython diese Optimierung nicht vornimmt, und dasselbe gilt für vollständig interpretierte Sprachen (obwohl dann der Overhead beim Parsen wahrscheinlich den Unterschied zwischen den beiden Maschinenbefehlen in den Schatten stellt). Es würde mich überraschen, wenn ich einen C- oder C++-Compiler finden würde, der dies nicht tut, wo es möglich ist, da es spätestens bei der Ausgabe von Anweisungen ein Selbstläufer ist.

Im Allgemeinen würde ich sagen, dass man in C++ der Optimierungsfähigkeit des Compilers völlig ausgeliefert ist. Standardcontainer und -algorithmen haben n Ebenen der Indirektion, von denen die meisten verschwinden, wenn der Compiler das Inlining und die Optimierung abgeschlossen hat. Ein anständiger C++-Compiler kann Arithmetik mit konstanten Werten noch vor dem Frühstück bewältigen, und ein nicht anständiger C++-Compiler wird miserablen Code produzieren, egal was Sie tun.

12voto

AnT Punkte 300728

Ihre Schlussfolgerung in Bezug auf die Leistung beruht auf der beliebten falschen Prämisse.

Aus irgendeinem Grund bestehen Sie darauf, die Sprachoperationen in ihre "offensichtlichen" maschinellen Entsprechungen zu übersetzen und auf der Grundlage dieser Übersetzung Schlussfolgerungen über die Leistung zu ziehen. In diesem speziellen Fall sind Sie zu dem Schluss gekommen, dass eine bitweise- und & der Sprache C++ muss durch eine Software implementiert werden, die bitweise und Maschinenbetrieb, während ein Modulo % der Vorgang muss in irgendeiner Weise eine Maschine Abteilung , die angeblich langsamer ist. Ein solcher Ansatz ist, wenn überhaupt, nur von sehr begrenztem Nutzen.

Erstens kann ich mir keinen realen C++-Compiler vorstellen, der die Sprachoperationen so "wörtlich" interpretiert, d. h. sie auf die "entsprechenden" Maschinenoperationen abbildet. Das liegt vor allem daran, dass die äquivalenten Maschinenoperationen häufiger, als man denken würde, einfach nicht existieren.

Wenn es um solche grundlegenden Operationen mit einer unmittelbaren Konstante als Operand geht, wird jeder anständige Compiler immer sofort "verstehen", dass sowohl num & 1 y num % 2 für Integral num tun genau das Gleiche, so dass der Compiler für beide Ausdrücke absolut identischen Code erzeugt. Natürlich wird die Leistung genau gleich sein.

Dies wird übrigens nicht "Optimierung" genannt. Eine Optimierung liegt definitionsgemäß vor, wenn der Compiler beschließt, vom Standardverhalten der abstrakten C++-Maschine abzuweichen, um einen effizienteren Code zu erzeugen (wobei das beobachtbare Verhalten des Programms erhalten bleibt). In diesem Fall gibt es keine Abweichung, d. h. es findet keine Optimierung statt.

Darüber hinaus ist es durchaus möglich, dass auf dem gegebenen Rechner weder das eine noch das andere optimal umgesetzt werden kann bitweise und noch Abteilung sondern eine andere spezielle maschinenspezifische Anweisung. Darüber hinaus ist es durchaus möglich, dass überhaupt keine Anweisung benötigt wird, da die Geradzahligkeit/Ungeradzahligkeit eines bestimmten Wertes durch die Statusflags des Prozessors oder ähnliches "kostenlos" offengelegt werden könnte.

Mit anderen Worten: Das Argument der Effizienz ist nicht stichhaltig.

Zweitens, um auf die ursprüngliche Frage zurückzukommen, ist die bessere Methode zur Bestimmung der Geradheit/Ungeradheit eines Wertes sicherlich die num % 2 Ansatz, da er die geforderte Prüfung wörtlich ("per definitionem") umsetzt und deutlich zum Ausdruck bringt, dass die Prüfung rein mathematisch ist. D.h. er macht deutlich, dass wir uns um die Eigenschaft eines Nummer und nicht über die Eigenschaft seiner Vertretung (wie im Falle von num & 1 Variante).

En num & 1 Variante sollte für Situationen reserviert werden, in denen Sie Zugriff auf die Bits der Wertdarstellung einer Zahl haben möchten. Die Verwendung dieses Codes zur Überprüfung der Geradzahligkeit bzw. Ungeradzahligkeit ist eine höchst fragwürdige Praxis.

12voto

Doug T. Punkte 61739

Ich definiere und verwende eine "IsEven"-Funktion, damit ich nicht darüber nachdenken muss, dann wähle ich die eine oder andere Methode und vergesse, wie ich prüfe, ob etwas gerade ist.

Einziges Manko ist, dass man bei der bitweisen Operation etwas über die Darstellung der Zahlen im Binärformat annimmt, bei modulo aber nicht. Sie interpretieren die Zahl als Dezimalwert. Das ist bei ganzen Zahlen so gut wie garantiert. Bedenken Sie jedoch, dass die Modulo-Operation bei einem Paschalwert funktionieren würde, die bitweise Operation jedoch nicht.

9voto

mrkj Punkte 2971

Es wurde bereits mehrfach erwähnt, dass jeder moderne Compiler für beide Optionen dieselbe Baugruppe erzeugen würde. Dies erinnerte mich an die LLVM-Demo-Seite das ich neulich irgendwo gesehen habe, also dachte ich, ich probiere es mal aus. Ich weiß, dass dies nicht viel mehr als eine Anekdote ist, aber es bestätigt, was wir erwarten würden: x%2 y x&1 werden identisch umgesetzt.

Ich habe auch versucht, diese beiden mit gcc-4.2.1 zu kompilieren ( gcc -S foo.c ) und die daraus resultierende Baugruppe ist identisch (und am Ende dieser Antwort eingefügt).

Programmieren Sie das erste:

int main(int argc, char **argv) {
  return (argc%2==0) ? 0 : 1;
}

Ergebnis:

; ModuleID = '/tmp/webcompile/_27244_0.bc'
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32"
target triple = "i386-pc-linux-gnu"

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind readnone {
entry:
    %0 = and i32 %argc, 1       ; <i32> [#uses=1]
    ret i32 %0
}

Programmieren Sie die zweite:

int main(int argc, char **argv) {
  return ((argc&1)==0) ? 0 : 1;
}

Ergebnis:

; ModuleID = '/tmp/webcompile/_27375_0.bc'
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32"
target triple = "i386-pc-linux-gnu"

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind readnone {
entry:
    %0 = and i32 %argc, 1       ; <i32> [#uses=1]
    ret i32 %0
}

GCC-Ausgabe:

.text
.globl _main
_main:
LFB2:
  pushq %rbp
LCFI0:
  movq  %rsp, %rbp
LCFI1:
  movl  %edi, -4(%rbp)
  movq  %rsi, -16(%rbp)
  movl  -4(%rbp), %eax
  andl  $1, %eax
  testl %eax, %eax
  setne %al
  movzbl  %al, %eax
  leave
  ret
LFE2:
  .section __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support
EH_frame1:
  .set L$set$0,LECIE1-LSCIE1
  .long L$set$0
LSCIE1:
  .long 0x0
  .byte 0x1
  .ascii "zR\0"
  .byte 0x1
  .byte 0x78
  .byte 0x10
  .byte 0x1
  .byte 0x10
  .byte 0xc
  .byte 0x7
  .byte 0x8
  .byte 0x90
  .byte 0x1
  .align 3
LECIE1:
.globl _main.eh
_main.eh:
LSFDE1:
  .set L$set$1,LEFDE1-LASFDE1
  .long L$set$1
ASFDE1:
  .long LASFDE1-EH_frame1
  .quad LFB2-.
  .set L$set$2,LFE2-LFB2
  .quad L$set$2
  .byte 0x0
  .byte 0x4
  .set L$set$3,LCFI0-LFB2
  .long L$set$3
  .byte 0xe
  .byte 0x10
  .byte 0x86
  .byte 0x2
  .byte 0x4
  .set L$set$4,LCFI1-LCFI0
  .long L$set$4
  .byte 0xd
  .byte 0x6
  .align 3
LEFDE1:
  .subsections_via_symbols

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X