Was ist deine liebste bitweise Technik? [geschlossen]


14

Vor einigen Tagen erkundigte sich StackExchange-Mitglied Anto nach gültigen Verwendungen für bitweise Operatoren. Ich stellte fest, dass die Verschiebung schneller war als das Multiplizieren und Teilen von ganzen Zahlen durch Zweierpotenzen. StackExchange-Mitglied Daemin konterte mit der Feststellung, dass Rechtsverschiebung Probleme mit negativen Zahlen aufwerfe.

Zu diesem Zeitpunkt hatte ich nie wirklich darüber nachgedacht, die Schichtoperatoren mit vorzeichenbehafteten ganzen Zahlen zu verwenden. Ich habe diese Technik hauptsächlich in der Low-Level-Softwareentwicklung eingesetzt. Daher habe ich immer ganze Zahlen ohne Vorzeichen verwendet. C führt logische Verschiebungen an vorzeichenlosen ganzen Zahlen durch. Beim Durchführen einer logischen Rechtsverschiebung wird dem Vorzeichenbit keine Beachtung geschenkt. Freie Bits werden mit Nullen gefüllt. C führt jedoch eine arithmetische Verschiebeoperation durch, wenn eine Ganzzahl mit Vorzeichen nach rechts verschoben wird. Freie Bits werden mit dem Vorzeichenbit gefüllt. Diese Differenz bewirkt, dass ein negativer Wert gegen Unendlich gerundet wird, anstatt gegen Null abgeschnitten zu werden. Dies ist ein anderes Verhalten als die Ganzzahldivision mit Vorzeichen.

Ein paar Minuten des Nachdenkens führten zu einer Lösung erster Ordnung. Die Lösung wandelt negative Werte vor dem Verschieben bedingt in positive Werte um. Ein Wert wird nach Ausführung der Verschiebung bedingt in seine negative Form zurückgewandelt.

int a = -5;
int n = 1;

int negative = q < 0; 

a = negative ? -a : a; 
a >>= n; 
a = negative ? -a : a; 

Das Problem bei dieser Lösung besteht darin, dass bedingte Zuweisungsanweisungen normalerweise in mindestens einen Sprungbefehl übersetzt werden und Sprungbefehle auf Prozessoren, die nicht beide Befehlspfade decodieren, teuer sein können. Eine Anweisungs-Pipeline zweimal neu vorbereiten zu müssen, wirkt sich positiv auf den Leistungszuwachs aus, der durch Verschieben über Teilen erzielt wird.

Mit dem oben Gesagten bin ich am Samstag mit der Antwort auf das Problem der bedingten Zuweisung aufgewacht. Das Rundungsproblem, das bei der Ausführung einer arithmetischen Verschiebungsoperation auftritt, tritt nur auf, wenn mit der Zweierkomplementdarstellung gearbeitet wird. Es kommt bei der eigenen Komplementdarstellung nicht vor. Die Lösung des Problems besteht darin, den Zweierkomplementwert vor dem Durchführen der Verschiebeoperation in einen Einerkomplementwert umzuwandeln. Wir müssen dann den Einerkomplementwert zurück in einen Zweikomplementwert umrechnen. Überraschenderweise können wir diesen Satz von Operationen ausführen, ohne negative Werte vor dem Ausführen der Verschiebeoperation bedingt zu konvertieren.

int a = -5;
int n = 1;

register int sign = (a >> INT_SIZE_MINUS_1) & 1

a = (a - sign) >> n + sign;   

Der negative Wert des Zweierkomplements wird durch Subtrahieren von Eins in den negativen Wert des Einerkomplements umgewandelt. Auf der anderen Seite wird der negative Wert eines Einerkomplements durch Addieren von Eins in den negativen Wert eines Zweikomplements umgewandelt. Der oben aufgeführte Code funktioniert, weil das Vorzeichenbit zum Konvertieren des Zweierkomplements in das eigene Komplement und umgekehrt verwendet wird . Nur bei negativen Werten werden die Vorzeichenbits gesetzt. Daher ist das variable Vorzeichen gleich Null, wenn a positiv ist.

Können Sie sich mit dem oben Gesagten andere bissige Hacks wie den oben genannten vorstellen, die es in Ihre Trickkiste geschafft haben? Was ist dein liebster bitweiser Hack? Ich bin immer auf der Suche nach neuen leistungsorientierten bitweisen Hacks.


3
Diese Frage und Ihr Kontoname - die Welt macht wieder Sinn ...
JK

+1 Interessante Frage als Follow-up zu mir und auch sonst;)
Anto

Ich habe auch einmal einige schnelle Paritätsberechnungen durchgeführt. Parität ist ein bisschen schmerzhaft, weil es traditionell Schleifen und Zählen beinhaltet, wenn ein Bit gesetzt ist, was alle viele Sprünge erfordert. Die Parität kann mithilfe von Shift und XOR berechnet werden. Eine Reihe von Schritten nacheinander vermeidet dann die Schleifen und Sprünge.
quick_now

2
Ist dir bewusst, dass es ein ganzes Buch über diese Techniken gibt? - Hackers Delight amazon.com/Hackers-Delight-Henry-S-Warren/dp/0201914654
Nikie

Ja, es gibt auch eine Website, die Bit-Operationen gewidmet ist. Ich habe die URL vergessen, aber Google wird sie früh genug anzeigen.
quick_now

Antworten:


23

Ich liebe Gospers Hack (HAKMEM # 175), eine sehr raffinierte Art, eine Zahl zu nehmen und die nächste Zahl mit der gleichen Anzahl von gesetzten Bits zu erhalten. Es ist zum Beispiel nützlich, um Kombinationen von kGegenständen aus nfolgenden Elementen zu generieren:

int set = (1 << k) - 1;
int limit = (1 << n);
while (set < limit) {
    doStuff(set);

    // Gosper's hack:
    int c = set & -set;
    int r = set + c;
    set = (((r^set) >>> 2) / c) | r;
}

7
+1. Aber von nun an werde ich Albträume haben, wenn ich diesen während einer Debugging-Sitzung ohne Kommentar finde.
Nikie

@nikie, muahahahaha! (Ich benutze dies eher für Dinge wie Project Euler-Probleme - mein Tagesjob beinhaltet nicht viel Kombinatorik).
Peter Taylor

7

Die Methode der schnellen inversen Quadratwurzel verwendet die bizarrsten Techniken auf Bitebene, um die Inverse einer Quadratwurzel zu berechnen, die ich je gesehen habe:

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking [sic]
    i  = 0x5f3759df - ( i >> 1 );               // what the fuck? [sic]
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
    //    y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}

Schnelles sqrt ist auch erstaunlich. Carmack scheint einer der größten Programmierer zu sein.
BenjaminB

Wikipedia hat noch ältere Quellen, zB beyond3d.com/content/articles/15
MSalters

0

Division durch 3 - ohne auf einen Laufzeitbibliotheksaufruf zurückzugreifen.

Es stellt sich heraus, dass die Division durch 3 (dank eines Hinweises auf Stack Overflow) angenähert werden kann als:

X / 3 = [(x / 4) + (x / 12)]

Und X / 12 ist (x / 4) / 3. Hier taucht plötzlich ein Element der Rekursion auf.

Es stellt sich außerdem heraus, dass Sie die Anzahl der erforderlichen Iterationen begrenzen können, wenn Sie den Bereich der Zahlen einschränken, in denen Sie spielen.

Für vorzeichenlose ganze Zahlen <2000 ist der folgende Algorithmus ein schneller und einfacher / 3-Algorithmus. (Für größere Zahlen fügen Sie einfach weitere Schritte hinzu). Compiler optimieren das Ganze so, dass es schnell und klein wird:

statische vorzeichenlose kurze FastDivide3 (const vorzeichenlose kurze arg)
{
  unsigned short RunningSum;
  unsigned short FractionalTwelth;

  RunningSum = arg >> 2;

  FractionalTwelth = RunningSum >> 2;
  RunningSum + = FractionalTwelth;

  FractionalTwelth >> = 2;
  RunningSum + = FractionalTwelth;

  FractionalTwelth >> = 2;
  RunningSum + = FractionalTwelth;

  FractionalTwelth >> = 2;
  RunningSum + = FractionalTwelth;

  // Weitere Wiederholungen der obigen 2 Zeilen für mehr Präzision

  return RunningSum;
}

1
Dies ist natürlich nur bei sehr undurchsichtigen Mikrocontrollern relevant. Jede echte CPU, die in den letzten zwei Jahrzehnten hergestellt wurde, benötigt keine Laufzeitbibliothek für die Ganzzahldivision.
MSalters

1
Na klar, aber kleine Mikros ohne Hardware-Multiplikator sind eigentlich sehr verbreitet. Und wenn Sie in Embedded Land arbeiten und bei jeder Million verkaufter Produkte 0,10 US-Dollar sparen möchten, sollten Sie einige schmutzige Tricks kennen. Das eingesparte Geld = zusätzlicher Gewinn, der Ihren Chef sehr glücklich macht.
quick_now

Naja, dreckig ... es multipliziert sich nur mit .0101010101(ca. 1/3). Pro - Tipp: Sie können auch mit .000100010001und multiplizieren 101(was nur 3 Bitverschiebungen dauert, aber die bessere Näherung hat.010101010101
MSalters 11.04.11

Wie kann ich das nur mit ganzen Zahlen und ohne Gleitkomma machen?
quick_now

1
Bitweise ist x * 101 = x + x << 2. In ähnlicher Weise ist x * 0,000100010001 x >> 4 + x >> 8 + x >> 12.
MSalters

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.