it-swarm.com.de

Berechnen Sie die Obergrenze für ein schnelles Protokoll von Basis 2

Wie kann der (long int) ceiling(log_2(i)) schnell berechnet werden, wenn Eingabe und Ausgabe 64-Bit-Ganzzahlen sind? Lösungen für vorzeichenbehaftete oder vorzeichenlose ganze Zahlen sind akzeptabel. Ich gehe davon aus, dass der beste Weg eine etwas zwielichtige Methode sein wird, die den hier gefundenen hier ähnlich ist, aber anstatt meine eigenen zu versuchen, möchte ich etwas verwenden, das bereits gut getestet wurde. Eine allgemeine Lösung funktioniert für alle positiven Werte.

Zum Beispiel sind die Werte für 2,3,4,5,6,7,8 1,2,2,3,3,3,3 

Edit: Bisher scheint es die beste Route zu sein, die Ganzzahl/Flur-Log-Basis 2 (die Position des MSB) mit einer beliebigen Anzahl von schnell vorhandenen Bithacks oder Registermethoden zu berechnen und dann eine hinzuzufügen, wenn die Eingabe keine Potenz von ist zwei. Die schnelle bitweise Prüfung auf Zweierpotenzen ist (n&(n-1)).

Edit 2: Eine gute Quelle für ganzzahlige Logarithmen und führende Nullmethoden sind die Abschnitte 5-3 und 11-4 in Hacker's Delight von Henry S. Warren. Dies ist die umfassendste Behandlung, die ich gefunden habe.

Edit 3: Diese Technik sieht vielversprechend aus: https://stackoverflow.com/a/51351885/365478

36
kevinlawler

Dieser Algorithmus wurde bereits veröffentlicht, aber die folgende Implementierung ist sehr kompakt und sollte sich in verzweigungsfreien Code optimieren.

int ceil_log2(unsigned long long x)
{
  static const unsigned long long t[6] = {
    0xFFFFFFFF00000000ull,
    0x00000000FFFF0000ull,
    0x000000000000FF00ull,
    0x00000000000000F0ull,
    0x000000000000000Cull,
    0x0000000000000002ull
  };

  int y = (((x & (x - 1)) == 0) ? 0 : 1);
  int j = 32;
  int i;

  for (i = 0; i < 6; i++) {
    int k = (((x & t[i]) == 0) ? 0 : j);
    y += k;
    x >>= k;
    j >>= 1;
  }

  return y;
}


#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
  printf("%d\n", ceil_log2(atol(argv[1])));

  return 0;
}
18
dgobbi

Wenn Sie sich auf gcc beschränken können, gibt es eine Reihe eingebauter Funktionen, die die Anzahl der führenden Null-Bits zurückgeben und mit etwas Arbeit dazu verwendet werden können, was Sie wollen:

int __builtin_clz (unsigned int x)
int __builtin_clzl (unsigned long)
int __builtin_clzll (unsigned long long)
19
ergosys

Wenn Sie für 64-Bit-Prozessoren unter Windows kompilieren, sollte dies meiner Meinung nach funktionieren. _BitScanReverse64 ist eine intrinsische Funktion.

#include <intrin.h>
__int64 log2ceil( __int64 x )
{
  unsigned long index;
  if ( !_BitScanReverse64( &index, x ) )
     return -1LL; //dummy return value for x==0

  // add 1 if x is NOT a power of 2 (to do the ceil)
  return index + (x&(x-1)?1:0);
}

Für 32-Bit können Sie _BitScanReverse64 mit 1 oder 2 Aufrufen an _BitScanReverse emulieren. Überprüfen Sie die oberen 32 Bits von x, ((long *) & x) [1] und dann die unteren 32 Bits benötigt, ((long *) & x) [0].

10
Tom Sirgedas
#include "stdafx.h"
#include "assert.h"

int getpos(unsigned __int64 value)
{
    if (!value)
    {
      return -1; // no bits set
    }
    int pos = 0;
    if (value & (value - 1ULL))
    {
      pos = 1;
    }
    if (value & 0xFFFFFFFF00000000ULL)
    {
      pos += 32;
      value = value >> 32;
    }
    if (value & 0x00000000FFFF0000ULL)
    {
      pos += 16;
      value = value >> 16;
    }
    if (value & 0x000000000000FF00ULL)
    {
      pos += 8;
      value = value >> 8;
    }
    if (value & 0x00000000000000F0ULL)
    {
      pos += 4;
      value = value >> 4;
    }
    if (value & 0x000000000000000CULL)
    {
      pos += 2;
      value = value >> 2;
    }
    if (value & 0x0000000000000002ULL)
    {
      pos += 1;
      value = value >> 1;
    }
    return pos;
}

int _tmain(int argc, _TCHAR* argv[])
{    
    assert(getpos(0ULL) == -1); // None bits set, return -1.
    assert(getpos(1ULL) == 0);
    assert(getpos(2ULL) == 1);
    assert(getpos(3ULL) == 2);
    assert(getpos(4ULL) == 2);
    for (int k = 0; k < 64; ++k)
    {
        int pos = getpos(1ULL << k);
        assert(pos == k);
    }
    for (int k = 0; k < 64; ++k)
    {
        int pos = getpos( (1ULL << k) - 1);
        assert(pos == (k < 2 ? k - 1 : k) );
    }
    for (int k = 0; k < 64; ++k)
    {
        int pos = getpos( (1ULL << k) | 1);
        assert(pos == (k < 1 ? k : k + 1) );
    }
    for (int k = 0; k < 64; ++k)
    {
        int pos = getpos( (1ULL << k) + 1);
        assert(pos == k + 1);
    }
    return 0;
}
5
rwong

Mit den von @egosys erwähnten gcc-Builtins können Sie einige nützliche Makros erstellen. Für eine schnelle und grobe Berechnung der Grundfläche (log2 (x)) können Sie Folgendes verwenden:

#define FAST_LOG2(x) (sizeof(unsigned long)*8 - 1 - __builtin_clzl((unsigned long)(x)))

Für eine ähnliche Decke (log2 (x)) verwenden Sie:

#define FAST_LOG2_UP(x) (((x) - (1 << FAST_LOG2(x))) ? FAST_LOG2(x) + 1 : FAST_LOG2(x))

Letzteres kann durch mehr Gcc-Eigenheiten weiter optimiert werden, um den doppelten Aufruf des Builtin zu vermeiden, aber ich bin nicht sicher, ob Sie es hier brauchen.

5

Das folgende Code-Snippet ist eine sichere und tragbare Möglichkeit, plain-C-Methoden, z. B. @ dgobbi, um Compiler-Intrinsics zu erweitern, wenn sie mit unterstützenden Compilern (Clang) kompiliert werden. Wenn Sie diese Option oben in der Methode platzieren, wird die integrierte Methode verwendet, wenn sie verfügbar ist. Wenn das integrierte Modul nicht verfügbar ist, wird die Methode auf den Standard-C-Code zurückgesetzt.

#ifndef __has_builtin
#define __has_builtin(x) 0
#endif

#if __has_builtin(__builtin_clzll) //use compiler if possible
  return ((sizeof(unsigned long long) * 8 - 1) - __builtin_clzll(x)) + (!!(x & (x - 1)));
#endif
4
kevinlawler

Der schnellste Ansatz, den ich kenne, verwendet einen schnellen log2, der abrundet und die bedingungslose Anpassung des Eingabewerts vor und nach dem Nachrunden kombiniert, um den Aufrundungsfall wie in lg_down() unten beschrieben zu behandeln.

/* base-2 logarithm, rounding down */
static inline uint64_t lg_down(uint64_t x) {
  return 63U - __builtin_clzl(x);
}

/* base-2 logarithm, rounding up */
static inline uint64_t lg_up(uint64_t x) {
  return lg_down(x - 1) + 1;
}

Grundsätzlich ist das Hinzufügen von 1 zum abgerundeten Ergebnis bereits für alle Werte korrekt, mit Ausnahme der exakten Zweierpotenz (da in diesem Fall die Ansätze floor und ceil dieselbe Antwort zurückgeben sollten), reicht es aus, 1 vom zu handhabenden Eingabewert abzuziehen diesen Fall (ändert die Antwort für die anderen Fälle nicht) und fügen Sie dem Ergebnis eine hinzu. 

Dies ist normalerweise etwas schneller als die Ansätze, die den Wert anpassen, indem explizit nach genauen Potenzen von zwei gesucht wird (z. B. durch Hinzufügen eines !!(x & (x - 1))-Terms). Es vermeidet Vergleiche und bedingte Operationen oder Verzweigungen, ist eher einfach beim Inlining, eher anfällig für Vektorisierung usw.

Dies beruht auf der "Count Lead Bits" -Funktionalität, die von den meisten CPUs mit dem Clang/icc/gcc __builtin_clzl angeboten wird. Andere Plattformen bieten jedoch etwas Ähnliches (z. B. die in Visual Studio intrinsic BitScanReverse).

Unglücklicherweise geben diese vielen die falsche Antwort für log(1) zurück, da dies zu __builtin_clzl(0) führt, was undefiniertes Verhalten ist, das auf der gcc-Dokumentation basiert. Natürlich hat die allgemeine Funktion "führende Nullen zählen" ein perfekt definiertes Verhalten bei Null, aber das gcc-Builtin ist auf diese Weise definiert, da vor der Erweiterung BMI ISA auf x86 die Funktion _ verwendet wurde. bsr-Anweisung die selbst undefiniertes Verhalten hat.

Sie können dies umgehen, wenn Sie wissen, dass Sie die lzcnt-Anweisung haben, indem Sie die lzcnt-Komponente direkt verwenden. Die meisten anderen Plattformen als x86 haben den bsr-Fehler nie durchgemacht und bieten wahrscheinlich auch Methoden für den Zugriff auf ihre "führende Nullen" -Anweisung an, wenn sie eine haben.

3
BeeOnRope

Die wahre schnellste Lösung:

Ein binärer Suchbaum mit 63 Einträgen. Dies sind die Potenzen von 2 von 0 bis 63. Einmalige Generierungsfunktion zum Erstellen des Baums. Die Blätter repräsentieren die Log-Basis 2 der Potenzen (im Grunde die Zahlen 1-63).

Um die Antwort zu finden, geben Sie eine Zahl in die Baumstruktur ein und navigieren Sie zu einem Blattknoten, der größer als das Element ist. Wenn der Blattknoten genau gleich ist, ist das Ergebnis der Blattwert. Andernfalls ist das Ergebnis der Blattwert + 1.

Die Komplexität ist auf O (6) festgelegt. 

3

Das Auffinden der Protokollbasis 2 einer Ganzzahl (64-Bit oder ein beliebiges anderes Bit) mit Ganzzahlausgabe entspricht dem Auffinden des höchsten gesetzten Bits. Warum? Da Protokollbasis 2 ist, wie oft Sie die Zahl durch 2 dividieren können, um 1 zu erreichen. 

Eine Möglichkeit, das eingestellte MSB zu finden, besteht darin, einfach jedes Mal um 1 Bits nach rechts zu verschieben, bis Sie 0 haben. Eine andere, effizientere Methode ist die Durchführung einer Art binärer Suche über Bitmasken.

Der Ceil-Teil lässt sich leicht ermitteln, indem geprüft wird, ob andere Bits als das MSB gesetzt sind.

2
Brian R. Bondy

Wenn Sie über 80-Bit- oder 128-Bit-Floats verfügen, wandeln Sie in diesen Typ um und lesen Sie die Exponenten-Bits ab. Dieser Link enthält Details (für ganze Zahlen bis zu 52 Bit) und verschiedene andere Methoden:

http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogIEEE64Float

Überprüfen Sie auch die ffmpeg-Quelle. Ich weiß, dass sie einen sehr schnellen Algorithmus haben. Selbst wenn es nicht direkt auf größere Größen erweiterbar ist, können Sie leicht etwas wie if (x>INT32_MAX) return fastlog2(x>>32)+32; else return fastlog2(x); tun.

1
R..

Ich habe mehrere Implementierungen eines 64-Bit-"höchsten Bits" getestet. Der "branchenfrei" Code ist nicht der schnellste.

Dies ist meine highest-bit.c-Quelldatei:

int highest_bit_unrolled(unsigned long long n)
{
  if (n & 0xFFFFFFFF00000000) {
    if (n & 0xFFFF000000000000) {
      if (n & 0xFF00000000000000) {
        if (n & 0xF000000000000000) {
          if (n & 0xC000000000000000)
            return (n & 0x8000000000000000) ? 64 : 63;
          else
            return (n & 0x2000000000000000) ? 62 : 61;
        } else {
          if (n & 0x0C00000000000000)
            return (n & 0x0800000000000000) ? 60 : 59;
          else
            return (n & 0x0200000000000000) ? 58 : 57;
        }
      } else {
        if (n & 0x00F0000000000000) {
          if (n & 0x00C0000000000000)
            return (n & 0x0080000000000000) ? 56 : 55;
          else
            return (n & 0x0020000000000000) ? 54 : 53;
        } else {
          if (n & 0x000C000000000000)
            return (n & 0x0008000000000000) ? 52 : 51;
          else
            return (n & 0x0002000000000000) ? 50 : 49;
        }
      }
    } else {
      if (n & 0x0000FF0000000000) {
        if (n & 0x0000F00000000000) {
          if (n & 0x0000C00000000000)
            return (n & 0x0000800000000000) ? 48 : 47;
          else
            return (n & 0x0000200000000000) ? 46 : 45;
        } else {
          if (n & 0x00000C0000000000)
            return (n & 0x0000080000000000) ? 44 : 43;
          else
            return (n & 0x0000020000000000) ? 42 : 41;
        }
      } else {
        if (n & 0x000000F000000000) {
          if (n & 0x000000C000000000)
            return (n & 0x0000008000000000) ? 40 : 39;
          else
            return (n & 0x0000002000000000) ? 38 : 37;
        } else {
          if (n & 0x0000000C00000000)
            return (n & 0x0000000800000000) ? 36 : 35;
          else
            return (n & 0x0000000200000000) ? 34 : 33;
        }
      }
    }
  } else {
    if (n & 0x00000000FFFF0000) {
      if (n & 0x00000000FF000000) {
        if (n & 0x00000000F0000000) {
          if (n & 0x00000000C0000000)
            return (n & 0x0000000080000000) ? 32 : 31;
          else
            return (n & 0x0000000020000000) ? 30 : 29;
        } else {
          if (n & 0x000000000C000000)
            return (n & 0x0000000008000000) ? 28 : 27;
          else
            return (n & 0x0000000002000000) ? 26 : 25;
        }
      } else {
        if (n & 0x0000000000F00000) {
          if (n & 0x0000000000C00000)
            return (n & 0x0000000000800000) ? 24 : 23;
          else
            return (n & 0x0000000000200000) ? 22 : 21;
        } else {
          if (n & 0x00000000000C0000)
            return (n & 0x0000000000080000) ? 20 : 19;
          else
            return (n & 0x0000000000020000) ? 18 : 17;
        }
      }
    } else {
      if (n & 0x000000000000FF00) {
        if (n & 0x000000000000F000) {
          if (n & 0x000000000000C000)
            return (n & 0x0000000000008000) ? 16 : 15;
          else
            return (n & 0x0000000000002000) ? 14 : 13;
        } else {
          if (n & 0x0000000000000C00)
            return (n & 0x0000000000000800) ? 12 : 11;
          else
            return (n & 0x0000000000000200) ? 10 : 9;
        }
      } else {
        if (n & 0x00000000000000F0) {
          if (n & 0x00000000000000C0)
            return (n & 0x0000000000000080) ? 8 : 7;
          else
            return (n & 0x0000000000000020) ? 6 : 5;
        } else {
          if (n & 0x000000000000000C)
            return (n & 0x0000000000000008) ? 4 : 3;
          else
            return (n & 0x0000000000000002) ? 2 : (n ? 1 : 0);
        }
      }
    }
  }
}

int highest_bit_bs(unsigned long long n)
{
  const unsigned long long mask[] = {
    0x000000007FFFFFFF,
    0x000000000000FFFF,
    0x00000000000000FF,
    0x000000000000000F,
    0x0000000000000003,
    0x0000000000000001
  };
  int hi = 64;
  int lo = 0;
  int i = 0;

  if (n == 0)
    return 0;

  for (i = 0; i < sizeof mask / sizeof mask[0]; i++) {
    int mi = lo + (hi - lo) / 2;

    if ((n >> mi) != 0)
      lo = mi;
    else if ((n & (mask[i] << lo)) != 0)
      hi = mi;
  }

  return lo + 1;
}

int highest_bit_shift(unsigned long long n)
{
  int i = 0;
  for (; n; n >>= 1, i++)
    ; /* empty */
  return i;
}

static int count_ones(unsigned long long d)
{
  d = ((d & 0xAAAAAAAAAAAAAAAA) >>  1) + (d & 0x5555555555555555);
  d = ((d & 0xCCCCCCCCCCCCCCCC) >>  2) + (d & 0x3333333333333333);
  d = ((d & 0xF0F0F0F0F0F0F0F0) >>  4) + (d & 0x0F0F0F0F0F0F0F0F);
  d = ((d & 0xFF00FF00FF00FF00) >>  8) + (d & 0x00FF00FF00FF00FF);
  d = ((d & 0xFFFF0000FFFF0000) >> 16) + (d & 0x0000FFFF0000FFFF);
  d = ((d & 0xFFFFFFFF00000000) >> 32) + (d & 0x00000000FFFFFFFF);
  return d;
}

int highest_bit_parallel(unsigned long long n)
{
  n |= n >> 1;
  n |= n >> 2;
  n |= n >> 4;
  n |= n >> 8;
  n |= n >> 16;
  n |= n >> 32;
  return count_ones(n);
}

int highest_bit_so(unsigned long long x)
{
  static const unsigned long long t[6] = {
    0xFFFFFFFF00000000ull,
    0x00000000FFFF0000ull,
    0x000000000000FF00ull,
    0x00000000000000F0ull,
    0x000000000000000Cull,
    0x0000000000000002ull
  };

  int y = (((x & (x - 1)) == 0) ? 0 : 1);
  int j = 32;
  int i;

  for (i = 0; i < 6; i++) {
    int k = (((x & t[i]) == 0) ? 0 : j);
    y += k;
    x >>= k;
    j >>= 1;
  }

  return y;
}

int highest_bit_so2(unsigned long long value)
{
  int pos = 0;
  if (value & (value - 1ULL))
  {
    pos = 1;
  }
  if (value & 0xFFFFFFFF00000000ULL)
  {
    pos += 32;
    value = value >> 32;
  }
  if (value & 0x00000000FFFF0000ULL)
  {
    pos += 16;
    value = value >> 16;
  }
  if (value & 0x000000000000FF00ULL)
  {
    pos += 8;
    value = value >> 8;
  }
  if (value & 0x00000000000000F0ULL)
  {
    pos += 4;
    value = value >> 4;
  }
  if (value & 0x000000000000000CULL)
  {
    pos += 2;
    value = value >> 2;
  }
  if (value & 0x0000000000000002ULL)
  {
    pos += 1;
    value = value >> 1;
  }
  return pos;
}

Dies ist highest-bit.h:

int highest_bit_unrolled(unsigned long long n);
int highest_bit_bs(unsigned long long n);
int highest_bit_shift(unsigned long long n);
int highest_bit_parallel(unsigned long long n);
int highest_bit_so(unsigned long long n);
int highest_bit_so2(unsigned long long n);

Und das Hauptprogramm (Entschuldigung für das Kopieren und Einfügen):

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "highest-bit.h"

double timedelta(clock_t start, clock_t end)
{
  return (end - start)*1.0/CLOCKS_PER_SEC;
}

int main(int argc, char **argv)
{
  int i;
  volatile unsigned long long v;
  clock_t start, end;

  start = clock();

  for (i = 0; i < 10000000; i++) {
    for (v = 0x8000000000000000; v; v >>= 1)
      highest_bit_unrolled(v);
  }

  end = clock();

  printf("highest_bit_unrolled = %6.3fs\n", timedelta(start, end));

  start = clock();

  for (i = 0; i < 10000000; i++) {
    for (v = 0x8000000000000000; v; v >>= 1)
      highest_bit_parallel(v);
  }

  end = clock();

  printf("highest_bit_parallel = %6.3fs\n", timedelta(start, end));

  start = clock();

  for (i = 0; i < 10000000; i++) {
    for (v = 0x8000000000000000; v; v >>= 1)
      highest_bit_bs(v);
  }

  end = clock();

  printf("highest_bit_bs = %6.3fs\n", timedelta(start, end));

  start = clock();

  for (i = 0; i < 10000000; i++) {
    for (v = 0x8000000000000000; v; v >>= 1)
      highest_bit_shift(v);
  }

  end = clock();

  printf("highest_bit_shift = %6.3fs\n", timedelta(start, end));

  start = clock();

  for (i = 0; i < 10000000; i++) {
    for (v = 0x8000000000000000; v; v >>= 1)
      highest_bit_so(v);
  }

  end = clock();

  printf("highest_bit_so = %6.3fs\n", timedelta(start, end));

  start = clock();

  for (i = 0; i < 10000000; i++) {
    for (v = 0x8000000000000000; v; v >>= 1)
      highest_bit_so2(v);
  }

  end = clock();

  printf("highest_bit_so2 = %6.3fs\n", timedelta(start, end));

  return 0;
}

Ich habe diese verschiedenen Intel x86-Boxen getestet, alte und neue.

Die highest_bit_unrolled (unrollierte binäre Suche) ist durchweg wesentlich schneller als highest_bit_parallel (verzweigungsfreie Bitoperationen). Dies ist schneller als highest_bit_bs (binäre Suchschleife), und das wiederum ist schneller als highest_bit_shift (naive Shift- und Count-Schleife).

highest_bit_unrolled ist auch schneller als die in der akzeptierten SO Antwort (highest_bit_so) und auch eine, die in einer anderen Antwort (highest_bit_so2) angegeben ist.

Der Benchmark durchläuft eine Ein-Bit-Maske, die aufeinanderfolgende Bits abdeckt. Dies ist der Versuch, die Verzweigungsvorhersage in der abgewickelten binären Suche zu unterdrücken, was realistisch ist: In einem realen Programm ist es unwahrscheinlich, dass die Eingabefälle die Lokalität der Bitposition aufweisen.

Hier sind Ergebnisse zu einer alten Intel(R) Core(TM)2 Duo CPU E4500 @ 2.20GHz:

$ ./highest-bit
highest_bit_unrolled =  6.090s
highest_bit_parallel =  9.260s
highest_bit_bs = 19.910s
highest_bit_shift = 21.130s
highest_bit_so =  8.230s
highest_bit_so2 =  6.960s

Bei einem neueren Modell Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz:

highest_bit_unrolled =  1.555s
highest_bit_parallel =  3.420s
highest_bit_bs =  6.486s
highest_bit_shift =  9.505s
highest_bit_so =  4.127s
highest_bit_so2 =  1.645s

Auf der neueren Hardware nähert sich highest_bit_so2 auf der neueren Hardware highest_bit_unrolled. Die Reihenfolge ist nicht ganz gleich. jetzt fällt highest_bit_so wirklich zurück und ist langsamer als highest_bit_parallel.

Der schnellste Code, highest_bit_unrolled, enthält den meisten Code mit den meisten Verzweigungen. Jeder einzelne Rückgabewert wird von einem anderen Satz von Bedingungen mit einem eigenen dedizierten Code erreicht.

Die Intuition von "alle Zweige meiden" (aus Angst vor Verzweigungsvorhersagen) ist nicht immer richtig. Moderne (und auch nicht mehr so ​​moderne) Prozessoren weisen eine beträchtliche List auf, um nicht durch Verzweigungen behindert zu werden.


P.S. highest_bit_unrolled wurde in der TXR-Sprache in Dezember 2011 (mit Fehlern, seit dem Debuggen) eingeführt.

Vor kurzem fragte ich mich, ob etwas schönerer, kompakterer Code ohne Verzweigungen möglicherweise nicht schneller ist.

Ich bin etwas überrascht von den Ergebnissen.

Der Code sollte wirklich #ifdefing für GNU C sein und einige Compiler-Primitive verwenden, aber was die Portabilität angeht, bleibt diese Version bestehen.

1
Kaz

Die naive lineare Suche kann eine Option für gleichmäßig verteilte Ganzzahlen sein, da sie im Durchschnitt etwas weniger als 2 Vergleiche benötigt (für jede Ganzzahlgröße).

/* between 1 and 64 comparisons, ~2 on average */
#define u64_size(c) (              \
    0x8000000000000000 < (c) ? 64  \
  : 0x4000000000000000 < (c) ? 63  \
  : 0x2000000000000000 < (c) ? 62  \
  : 0x1000000000000000 < (c) ? 61  \
...
  : 0x0000000000000002 < (c) ?  2  \
  : 0x0000000000000001 < (c) ?  1  \
  :                             0  \
)
1
Loic

Der folgende Code ist einfacher und funktioniert so lange, wie die Eingabe x> = 1. Die Eingabe clog2 (0) liefert eine undefinierte Antwort (was sinnvoll ist, weil log (0) unendlich ist ...). x == 0) wenn Sie möchten:

unsigned int clog2 (unsigned int x)
{
    unsigned int result = 0;
    --x;
    while (x > 0) {
        ++result;
        x >>= 1;
    }

    return result;
}

Übrigens ist der Code für das Stockwerk von log2 ähnlich: (Wieder unter der Annahme, dass x> = 1 ist)

unsigned int flog2 (unsigned int x)
{
    unsigned int result = 0;
    while (x > 1) {
        ++result;
        x >>= 1;
    }

    return result;
}
0
user3225538

Ich gebe Ihnen zum Zeitpunkt des Schreibens den schnellsten Weg für x86-64 und eine allgemeine Technik, wenn Sie einen schnellen Fußboden haben, der für Argumente <2 ^ 63 funktioniert, wenn Sie sich für den gesamten Bereich interessieren. dann weiter unten.

Ich bin überrascht über die schlechte Qualität der anderen Antworten, weil sie Ihnen sagen, wie Sie den Boden erreichen, aber den Boden auf sehr teure Weise (unter Verwendung von Conditionals und allem!) An die Decke bringen.

Wenn Sie den Boden des Logarithmus schnell ermitteln können, beispielsweise mit __builtin_clzll, können Sie den Boden sehr einfach wie folgt erhalten:

unsigned long long log2Floor(unsigned long long x) {
    return 63 - __builtin_clzll(x);
}

unsigned long long log2Ceiling(unsigned long long x) {
    return log2Floor(2*x - 1);
}

Es funktioniert, weil es dem Ergebnis 1 hinzufügt, es sei denn, x ist genau eine Potenz von 2.

Siehe den x86-64-Assembler-Unterschied im Compiler-Explorer für eine andere Implementierung von ceiling wie folgt:

auto log2CeilingDumb(unsigned long x) {
    return log2Floor(x) + (!!(x & (x - 1)));
}

Gibt:

log2Floor(unsigned long): # @log2Floor(unsigned long)
  bsr rax, rdi
  ret
log2CeilingDumb(unsigned long): # @log2CeilingDumb(unsigned long)
  bsr rax, rdi
  lea rcx, [rdi - 1]
  and rcx, rdi
  cmp rcx, 1
  sbb eax, -1
  ret
log2Ceiling(unsigned long): # @log2Ceiling(unsigned long)
  lea rax, [rdi + rdi]
  add rax, -1
  bsr rax, rax
  ret

Für den gesamten Bereich steht dies in einer früheren Antwort: return log2Floor(x - 1) + 1. Dies ist erheblich langsamer, da in x86-64 vier Anweisungen verwendet werden, im Vergleich zu den drei obigen.

0
TheCppZoo