C'deki bir tamsayıda en yüksek ayarlanmış biti (msb) bulmanın en hızlı / en verimli yolu nedir?


119

Bir n tamsayısına sahipsem ve en anlamlı bitin konumunu bilmek istiyorsam (yani, en az anlamlı bit sağdaysa, en uzaktaki 1 olan en soldaki bitin konumunu bilmek istiyorum), bulmanın en hızlı / en verimli yöntemi nedir?

ffs()POSIX'in strings.h'deki ilk biti bulmak için bir yöntemi desteklediğini biliyorum , ancak buna karşılık gelen bir fls()yöntem yok gibi görünüyor .

Bunu yapmanın kaçırdığım gerçekten açık bir yolu var mı?

Taşınabilirlik için POSIX işlevlerini kullanamadığınız durumlarda ne olur?

Düzenleme: Hem 32 hem de 64 bit mimarilerde çalışan bir çözüme ne dersiniz (kod listelerinin çoğu, yalnızca 32 bit ints üzerinde çalışıyormuş gibi görünüyor).


Burada birkaç uygulama var: graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightLinear (Düzenleme: Sorunuzu tekrar okuduktan sonra, yukarıdaki bağlantının, ihtiyaç duyduğunuz kadar soldaki değil, en sağdaki seti bulmak için olduğunu fark ettim. kelime boyutu anlamında, cevaplaması zor bir soru)
spender


Bu, sağdaki sıfırları sayar ; soru soldaki sıfırlarla ilgiliydi. En azından, hızlı bir göz atmada onu orada görmüyorum.
Darius Bacon

2
Özellikle bit sayısını 'n' ister misiniz, yoksa 2 ^ n yeterli olur mu?
Alnitak

1
"Log Base 2" algoritmalarına bakın - Anderson'ın makalesinde dediği gibi: "Bir tamsayının günlük tabanı 2, en yüksek bit kümesinin (veya en önemli bit kümesi, MSB) konumuyla aynıdır"
Michael Burr

Yanıtlar:


64

GCC'nin sahip olduğu :

 - Yerleşik İşlev: int __builtin_clz (unsigned int x)
     En çok başlayarak X'in başındaki 0 ​​bitlerin sayısını verir
     önemli bit konumu. X 0 ise, sonuç tanımsızdır.

 - Yerleşik İşlev: int __builtin_clzl (unsigned long)
     "__Builtin_clz" ile benzerdir, ancak bağımsız değişken türü işaretsizdir
     uzun'.

 - Yerleşik İşlev: int __builtin_clzll (unsigned long long)
     "__Builtin_clz" ile benzerdir, ancak bağımsız değişken türü işaretsizdir
     uzunca'.

İster bu süslü bit döndürme algoritmalarından biri, ister tek bir talimat olsun, mevcut platformunuz için makul derecede verimli bir şeye dönüştürülmelerini beklerim.


Yararlı bir hüner girişinizi eğer edebilirsiniz olmak sıfırdır __builtin_clz(x | 1): koşulsuz herhangi diğerlerini değiştirmeden düşük bit ayarı çıkışı yapar 31için x=0başka girişi için çıkışını değiştirmeden.

Bunu yapmaktan kaçınmak için, diğer seçeneğiniz ARM GCC'ler __clz(başlık gerekmez) veya talimatı _lzcnt_u32destekleyen CPU'lar üzerindeki x86'lar gibi platforma özgü içsel bilgilerdir lzcnt. ( Sıfır olmayan girişler için 31-lzcnt veren hata yerine eski CPU'larda lzcntolduğu gibi bsrkodu çözdüğüne dikkat edin .)

Ne yazık ki, x86 olmayan platformlarda girdi = 0 için sonucu 32 veya 64 (işlenen genişliğine göre) olarak tanımlayan çeşitli CLZ talimatlarından taşınabilir bir şekilde yararlanmanın bir yolu yoktur. x86 da lzcntbunu yapar ve bsrkullanmadığınız sürece derleyicinin çevirmesi gereken bir bit dizini üretir 31-__builtin_clz(x).

("Tanımlanmamış sonuç" C Tanımlanmamış Davranış değildir, sadece tanımlanmamış bir değerdir. Aslında komut çalıştırıldığında hedef kayıt defterinde bulunan her şeydir. AMD bunu belgeliyor, Intel yapmıyor, ancak Intel'in CPU'ları bu davranışı uyguluyor . Ama oluyor değil genellikle gcc asm içine C döndüğünde işlerin nasıl olduğunu, size atıyorsanız C değişkeninde önceden ne olursa olsun. Ayrıca bkz Neden olsun LZCNT "girdi bağımlılığı" kırılma geliyor? )



1
Sıfırın üzerinde tanımsız davranışı, LZCNT kullanılamadığında bile x86'da tek bir BSR talimatına derlemelerine izin verir. Bu, girdi-sıfır durumunu işlemek için bir BSF ve bir CMOV'a derleyen __builtin_ctzover için büyük bir avantajdır ffs. Yeterince kısa bir uygulamaya sahip olmayan mimarilerde (örn. clzTalimat olmadan eski ARM ), gcc, libgcc yardımcı işlevine bir çağrı gönderir.
Peter Cordes

41

X86'da olduğunuzu ve bir parça satır içi derleyici için oyun oynadığınızı varsayarsak, Intel bir BSRtalimat sağlar ("ters bit taraması"). Bu var oruç üzerine bazı (diğerleri üzerinde microcoded) x86s. Kılavuzdan:

En önemli set biti (1 bit) için kaynak işleneni arar. En anlamlı 1 bit bulunursa, bit dizini hedef işlenen içinde saklanır. Kaynak işlenen, bir kayıt veya hafıza konumu olabilir; hedef işlenen bir kayıttır. Bit indeksi, kaynak işlenenin 0 bitinden işaretsiz bir ofsettir. İçerik kaynağı işleneni 0 ise, hedef işlenenin içeriği tanımsızdır.

(PowerPC üzerindeyseniz, benzer bir cntlz("önde gelen sıfırları say") talimatı vardır.)

Gcc için örnek kod:

#include <iostream>

int main (int,char**)
{
  int n=1;
  for (;;++n) {
    int msb;
    asm("bsrl %1,%0" : "=r"(msb) : "r"(n));
    std::cout << n << " : " << msb << std::endl;
  }
  return 0;
}

Ayrıca , (bölüm 9.4) döngü kodundan önemli ölçüde daha hızlı olduğunu gösteren bu satır içi derleyici öğreticisine bakın.


4
Aslında bu talimat genellikle bir döngü halinde mikro kodlanmıştır ve oldukça yavaştır.
rlbond

2
Hangisi ? BSR veya CNTLZ? Yukarıda atıfta bulunulan x86-timing.pdf dosyasını okuduğum için, BSR sadece Netburst Pentium'larda yavaştır. Yine de PowerPC hakkında hiçbir şey bilmiyorum.
timday

5
... Tamam, daha yakından incelendiğinde, "BSR yalnızca P3 / Pentium-M / Core2 x86'larda hızlıdır". Netburst ve AMD'de yavaş.
timday

1
Sadece bir uyarı: Son iki bağlantınız öldü.
Baum mit Augen

2
@rlbond: huh, P4 Prescott üzerindeki BSR, 4c aktarım başına bir olmak üzere 16 döngü gecikmeli (!) 2 uops. Ancak daha önceki Netburst'ta, yalnızca 4 döngü gecikmesi (hala 2 uop) ve 2c aktarım hızı başına bir. (kaynak: agner.org/optimize ). Çoğu CPU'da, çıkışına da gcc'nin hesaba katmadığı bir bağımlılığı vardır (girdi sıfır olduğunda, asıl davranış hedefi değiştirmeden bırakmaktır). Bu, stackoverflow.com/questions/25078285/… gibi sorunlara yol açabilir . IDK gcc bunu düzeltirken BSR'yi neden kaçırdı?
Peter Cordes

38

2 ^ N, yalnızca N'inci bit kümesine (1 << N) sahip bir tamsayı olduğundan, en yüksek ayarlanmış bitin konumunu (N) bulmak, bu tamsayının tamsayı günlük 2 tabanıdır.

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

unsigned int v;
unsigned r = 0;

while (v >>= 1) {
    r++;
}

Bu "açık" algoritma herkes için şeffaf olmayabilir, ancak kodun en soldaki bit kaydırılana kadar art arda bir bit sağa kaydığını fark ettiğinizde (C'nin sıfır olmayan herhangi bir değeri doğru kabul ettiğini unutmayın) ve sayıyı döndürür. vardiyalar, mükemmel bir anlam ifade ediyor. Aynı zamanda birden fazla bit ayarlandığında bile çalıştığı anlamına gelir - sonuç her zaman en önemli bit içindir.

Bu sayfada aşağı kaydırırsanız, daha hızlı, daha karmaşık varyasyonlar vardır. Bununla birlikte, önde gelen sıfırların çok olduğu sayılarla uğraştığınızı biliyorsanız, naif yaklaşım kabul edilebilir hız sağlayabilir, çünkü C'de bit kaydırma oldukça hızlıdır ve basit algoritma bir diziyi indekslemeyi gerektirmez.

NOT: 64 bit değerleri kullanırken, ekstra akıllı algoritmalar kullanma konusunda son derece dikkatli olun; çoğu yalnızca 32 bitlik değerler için doğru şekilde çalışır.


2
@Johan Bir hata ayıklayıcı ile adım atmak, döngünün neden çıktığını açıklamaya yardımcı olabilir. Temel olarak, son 1 bit sağa kaydırıldıktan sonra koşuldaki ifade 0 olarak değerlendirilir (yanlış olarak kabul edilir).
Quinn Taylor

2
Son sonucu böyle kullanmak güzel fikir :)
Johan

6
not: işaretsiz olmalıdır, işaretli tam sayılar için negatif sayılar için sağa kaydırma başarısız olur.
Xantix

2
Xantix: C / C ++ 'daki değişim mantıksal bir değişimdir, bu yüzden iyi çalışıyor. Java, JavaScript veya D için mantıksal kaydırma operatörünü kullanmanız gerekir >>>. Artı muhtemelen karşılaştırıcı != 0ve belirtilmemiş bazı parantez sayısı.
Chase

8
@Chase: Hayır değil. İmzasızlar için mantıksal bir değişim . İçin imzalanmış , bu ya da olmayabilir mantıklı vardiya olmak (ve aslında, genellikle aritmetik).
Tim Čas

17

Şimşek hızında olmalı:

int msb(unsigned int v) {
  static const int pos[32] = {0, 1, 28, 2, 29, 14, 24, 3,
    30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19,
    16, 7, 26, 12, 18, 6, 11, 5, 10, 9};
  v |= v >> 1;
  v |= v >> 2;
  v |= v >> 4;
  v |= v >> 8;
  v |= v >> 16;
  v = (v >> 1) + 1;
  return pos[(v * 0x077CB531UL) >> 27];
}

25
7 bitlik kaymalar, 5 veya talimatlar, bir çoklu ve potansiyel bir önbellek eksik. :) Bunu kıyasladınız mı yoksa oluşturulan assembler'a mı baktınız? Bu olabilir derleyici ortadan kaldırabilir ne kadarını bağlı oldukça yavaş sonunda.
jalf

5
Ben burada yeniyim. Olumsuz oyları alamıyorum çocuklar. Tek yanıtı gerçekten işe yarayan kaynak koduyla sağladım.
Karakter

9
"Olası önbellek atlaması" büyük olasılıkla bu kodun arama tablosuna erişim gerektirmesinden kaynaklanmaktadır. Bu çağrıldığında bu tablo önbelleğe alınmazsa, getirilirken bir durma olacaktır. Bu, en kötü durum performansını LUT kullanmayan çözümlerden çok daha kötü hale getirebilir.
gevşeyin

13
asıl mesele değil. Gerekenden çok daha fazla veri önbelleği (hatta birden fazla önbellek satırı) ve gerekenden daha fazla talimat önbelleği kullanır. Muhtemelen işlevi ilk kez çağırdığınızda önlenebilecek olan önbellek kayıpları yaşayacaksınız ve bu, önbelleği gereğinden fazla kirletecektir, bu nedenle aramadan sonra , diğer kodlar gereğinden fazla yanlışla karşılaşabilir. LUT'lar genellikle zahmete değmez çünkü önbellek eksiklikleri pahalıdır. Ama sadece "yıldırım hızında" olduğunu iddia etmeden önce kıyaslamak istediğim bir şey olduğunu söyledim. Öyle değil o kesinlikle bir sorun.
jalf

6
Tabloda 32 girdi vardır ve her değer <255 (127) 'dir, bu nedenle tabloyu işaretsiz karakter türü olarak tanımlayın ve tek bir 32 bayt L1 önbellek satırına sığacaktır. Ve her şey iki önbellek satırına sığar.
ChuckCottrill

16

Bu, bir tür tamsayı günlüğü bulmak gibi bir şey. Ufak tefek numaralar var ama bunun için kendi aracımı yaptım. Tabii ki amaç hız içindir.

Benim farkına vardığım şey, CPU'nun tamsayının dönüşümü yüzdürmek için kullanılan otomatik bir bit algılayıcısına sahip olduğudur! Öyleyse bunu kullan.

double ff=(double)(v|1);
return ((*(1+(uint32_t *)&ff))>>20)-1023;  // assumes x86 endianness

Bu sürüm, değeri bir ikiye çevirir, ardından üssü okur, bu da size bitin nerede olduğunu söyler. Fantezi kayma ve çıkarma, IEEE değerinden uygun parçaları çıkarmaktır.

Kayar sayı kullanmak biraz daha hızlıdır, ancak bir kayan nokta size daha küçük hassasiyeti nedeniyle yalnızca ilk 24 bit konumunu verebilir.


Bunu güvenli bir şekilde, C ++ veya C'de tanımsız davranış olmadan yapmak için, memcpyyazım-bulma için işaretçi dönüştürme yerine kullanın . Derleyiciler, onu verimli bir şekilde satır içi yapmayı bilir.

// static_assert(sizeof(double) == 2 * sizeof(uint32_t), "double isn't 8-byte IEEE binary64");
// and also static_assert something about FLT_ENDIAN?

double ff=(double)(v|1);

uint32_t tmp;
memcpy(&tmp, ((const char*)&ff)+sizeof(uint32_t), sizeof(uint32_t));
return (tmp>>20)-1023;

Veya C99 ve sonrasında, a union {double d; uint32_t u[2];};. Ancak, C ++ 'da birleşim tipi punning'in yalnızca bazı derleyicilerde uzantı olarak desteklendiğini, ISO C ++' da desteklenmediğini unutmayın.


Bu, genellikle önde gelen sıfırlar sayma talimatı için platforma özgü bir özden daha yavaş olacaktır, ancak taşınabilir ISO C'nin böyle bir işlevi yoktur. Bazı CPU'larda baştaki sıfır sayma talimatı da yoktur, ancak bunlardan bazıları tam sayıları verimli bir şekilde double. Tam sayıya geri dönen bir FP bit deseninin yazılması yavaş olabilir (örneğin, PowerPC'de bir saklama / yeniden yükleme gerektirir ve genellikle bir yük-vurma-mağaza durmasına neden olur).

Bu algoritma SIMD uygulamaları için potansiyel olarak yararlı olabilir çünkü daha az CPU SIMD'ye sahiptir lzcnt. x86 yalnızca AVX512CD ile böyle bir talimat aldı


2
Evet. Ve gcc, tür-aliasing optimizasyonları nedeniyle -O2 ile böyle bir kodla kötü şeyler yapacak.
MSN

4
x86
CPU'larda

1
Evet, FPU maliyetleri yüksek. Ancak gerçek zaman ölçümleri, bunun tüm bit işlemlerinden veya özellikle tüm döngülerden daha hızlı olduğunu gösterdi. Deneyin ve en hızlısı her zaman en iyi tavsiyedir. Bununla birlikte GCC ve -O2 ile ilgili bir sorun yaşamadım.
SPWorley

1
Bu tanımsız davranış değil mi (uyumsuz tipte bir işaretçi aracılığıyla bir değeri okumak)?
dreamlax

3
Hacker's Delight, 5-3 Sayma Başlangıcı 0'larda 32 bitlik kaymalardaki hatanın nasıl düzeltileceğini açıklar. AsFloat ve asInt ile örtüşmek için anonim bir birleşim kullanan kodları şöyledir: k = k & ~ (k >> 1); asFloat = (float) k + 0.5f; n = 158 - (asInt >> 23); (ve evet, bu uygulama tanımlı davranışa dayanır)
D Coetzee

11

Kaz Kylheku burada

Bunun için 63 bitlik sayıların (gcc x86_64'teki uzun uzun tip) işaret bitinden uzak durarak iki yaklaşımı karşılaştırdım.

(Bir şey için buna "en yüksek biti bul" a ihtiyacım var, görüyorsunuz.)

Veriye dayalı ikili aramayı uyguladım (yukarıdaki cevaplardan birine yakın bir şekilde). Ayrıca el ile tamamen açılmış bir karar ağacı uyguladım, bu sadece acil işlenenlerle koddur. Döngü yok, tablo yok.

Karar ağacı (en yüksek_bit_unrolled), ikili aramanın açık bir teste sahip olduğu n = 0 durumu dışında,% 69 daha hızlı olacak şekilde karşılaştırıldı.

İkili aramanın 0 durum için özel testi, özel bir testi olmayan karar ağacından yalnızca% 48 daha hızlıdır.

Derleyici, makine: (GCC 4.5.2, -O3, x86-64, 2867 Mhz Intel Core i5).

int highest_bit_unrolled(long long n)
{
  if (n & 0x7FFFFFFF00000000) {
    if (n & 0x7FFF000000000000) {
      if (n & 0x7F00000000000000) {
        if (n & 0x7000000000000000) {
          if (n & 0x4000000000000000)
            return 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(long long n)
{
  const 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;
}

Hızlı ve kirli test programı:

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

int highest_bit_unrolled(long long n);
int highest_bit(long long n);

main(int argc, char **argv)
{
  long long n = strtoull(argv[1], NULL, 0);
  int b1, b2;
  long i;
  clock_t start = clock(), mid, end;

  for (i = 0; i < 1000000000; i++)
    b1 = highest_bit_unrolled(n);

  mid = clock();

  for (i = 0; i < 1000000000; i++)
    b2 = highest_bit(n);

  end = clock();

  printf("highest bit of 0x%llx/%lld = %d, %d\n", n, n, b1, b2);

  printf("time1 = %d\n", (int) (mid - start));
  printf("time2 = %d\n", (int) (end - mid));
  return 0;
}

Yalnızca -O2 kullanıldığında fark daha da artar. Karar ağacı neredeyse dört kat daha hızlı.

Ayrıca saf bit değiştirme koduna karşı da kıyaslama yaptım:

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

Bekleneceği gibi, bu yalnızca küçük sayılar için hızlıdır. En yüksek bitin n == 1 için 1 olduğunu belirlerken,% 80'den daha hızlı kıyaslama yaptı. Bununla birlikte, 63 bitlik alanda rastgele seçilen sayıların yarısı 63. bit setine sahiptir!

0x3FFFFFFFFFFFFFFF girişinde, karar ağacı sürümü 1'de olduğundan biraz daha hızlıdır ve bit kaydırıcıdan% 1120 daha hızlı (12,2 kat) olduğunu gösterir.

Ayrıca karar ağacını GCC yerleşikleriyle kıyaslayacağım ve aynı sayıya karşı tekrarlamak yerine girdilerin bir karışımını deneyeceğim. Devam eden bazı yapışan dal tahminleri ve belki de bazı gerçekçi olmayan önbellekleme senaryoları olabilir, bu da onu tekrarlarda yapay olarak daha hızlı yapar.


9
Bunun iyi olmadığını söylemiyorum, ancak buradaki test programınız sadece aynı sayıyı test ediyor, 2-3 yinelemeden sonra dal tahminlerini nihai konumlarına ayarlayacak ve bundan sonra mükemmel dal tahminleri yapacaklar. İşin iyi yanı, tamamen rastgele bir dağılımla, sayıların yarısının mükemmel tahmine, yani bit63'e yakın olmasıdır.
Surt

8

Ne dersin

int highest_bit(unsigned int a) {
    int count;
    std::frexp(a, &count);
    return count - 1;
}

?


Bu, bu cevabın yavaş (ancak daha taşınabilir) bir versiyonudur ve neden işe yaradığını açıklar.
Peter Cordes

6
unsigned int
msb32(register unsigned int x)
{
        x |= (x >> 1);
        x |= (x >> 2);
        x |= (x >> 4);
        x |= (x >> 8);
        x |= (x >> 16);
        return(x & ~(x >> 1));
}

1 kayıt, 13 talimat. İster inanın ister inanmayın, bu genellikle doğrusal zamanda çalışan yukarıda belirtilen BSR talimatından daha hızlıdır. Bu logaritmik zamandır.

Http://aggregate.org/MAGIC/#Most%20Significant%201%20Bit adresinden


7
Yukarıdaki kod soruyu cevaplamıyor. X'teki bit üzerindeki en anlamlı bitin açık kaldığı ve diğer tüm bitlerin kapatıldığı işaretsiz bir tamsayı döndürür. Soru, en önemli olanın konumunu bit üzerinde döndürmekti .
Karakter

3
Daha sonra, ayarlanan bitin dizinini bulmak için bir De Bruijn dizisi yaklaşımı kullanabilirsiniz. :-)
R .. GitHub BUZA YARDIM ETMEYİ DURDUR

5
@ Protagonist, ya yeterli olduğunu bir yorumda söyledi.
rlbond

Bu (aynı sayfadan) ihtiyacınız olan şeyi yapar, ancak ek bir işlev gerektirir. aggregate.org/MAGIC/#Log2%20of%20an%20Integer
Quinn Taylor

1
BSR, en azından Core2'den beri Intel CPU'larda hızlıdır. LZCNT, AMD CPU'larda hızlıdır ve gcc, __builtin_clzetkinleştirilmişse -march=nativeveya başka bir şey için kullanır (çünkü onu destekleyen her CPU'da hızlıdır). BSR'nin "yavaş" olduğu AMD Bulldozer ailesi gibi CPU'larda bile, o kadar yavaş değil: 4 döngü gecikmeli 7 m-op ve 4c işlem hacmi başına bir. Atom'da BSR gerçekten yavaştır: 16 döngü. Silvermont'ta, 10 döngü gecikmesiyle 10 ups. Bu, Silvermont'taki BSR'den biraz daha düşük gecikme olabilir, ancak IDK olabilir.
Peter Cordes

6

İşte şu anda bu sayfada verilen algoritmaların bazı (basit) karşılaştırmaları ...

Algoritmalar, işaretsiz int'in tüm girdileri üzerinde test edilmemiştir; bu yüzden körü körüne kullanmadan önce bunu kontrol edin;)

Makinemde clz (__builtin_clz) ve asm en iyi şekilde çalışır. asm, clz'den daha hızlı görünüyor ... ancak basit kıyaslama nedeniyle olabilir ...

//////// go.c ///////////////////////////////
// compile with:  gcc go.c -o go -lm
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/***************** math ********************/

#define POS_OF_HIGHESTBITmath(a) /* 0th position is the Least-Signif-Bit */    \
  ((unsigned) log2(a))         /* thus: do not use if a <= 0 */  

#define NUM_OF_HIGHESTBITmath(a) ((a)               \
                  ? (1U << POS_OF_HIGHESTBITmath(a))    \
                  : 0)



/***************** clz ********************/

unsigned NUM_BITS_U = ((sizeof(unsigned) << 3) - 1);
#define POS_OF_HIGHESTBITclz(a) (NUM_BITS_U - __builtin_clz(a)) /* only works for a != 0 */

#define NUM_OF_HIGHESTBITclz(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITclz(a))  \
                 : 0)


/***************** i2f ********************/

double FF;
#define POS_OF_HIGHESTBITi2f(a) (FF = (double)(ui|1), ((*(1+(unsigned*)&FF))>>20)-1023)


#define NUM_OF_HIGHESTBITi2f(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITi2f(a))  \
                 : 0)




/***************** asm ********************/

unsigned OUT;
#define POS_OF_HIGHESTBITasm(a) (({asm("bsrl %1,%0" : "=r"(OUT) : "r"(a));}), OUT)

#define NUM_OF_HIGHESTBITasm(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITasm(a))  \
                 : 0)




/***************** bitshift1 ********************/

#define NUM_OF_HIGHESTBITbitshift1(a) (({   \
  OUT = a;                  \
  OUT |= (OUT >> 1);                \
  OUT |= (OUT >> 2);                \
  OUT |= (OUT >> 4);                \
  OUT |= (OUT >> 8);                \
  OUT |= (OUT >> 16);               \
      }), (OUT & ~(OUT >> 1)))          \



/***************** bitshift2 ********************/
int POS[32] = {0, 1, 28, 2, 29, 14, 24, 3,
             30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19,
             16, 7, 26, 12, 18, 6, 11, 5, 10, 9};

#define POS_OF_HIGHESTBITbitshift2(a) (({   \
  OUT = a;                  \
  OUT |= OUT >> 1;              \
  OUT |= OUT >> 2;              \
  OUT |= OUT >> 4;              \
  OUT |= OUT >> 8;              \
  OUT |= OUT >> 16;             \
  OUT = (OUT >> 1) + 1;             \
      }), POS[(OUT * 0x077CB531UL) >> 27])

#define NUM_OF_HIGHESTBITbitshift2(a) ((a)              \
                       ? (1U << POS_OF_HIGHESTBITbitshift2(a)) \
                       : 0)



#define LOOPS 100000000U

int main()
{
  time_t start, end;
  unsigned ui;
  unsigned n;

  /********* Checking the first few unsigned values (you'll need to check all if you want to use an algorithm here) **************/
  printf("math\n");
  for (ui = 0U; ui < 18; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITmath(ui));

  printf("\n\n");

  printf("clz\n");
  for (ui = 0U; ui < 18U; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITclz(ui));

  printf("\n\n");

  printf("i2f\n");
  for (ui = 0U; ui < 18U; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITi2f(ui));

  printf("\n\n");

  printf("asm\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITasm(ui));
  }

  printf("\n\n");

  printf("bitshift1\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift1(ui));
  }

  printf("\n\n");

  printf("bitshift2\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift2(ui));
  }

  printf("\n\nPlease wait...\n\n");


  /************************* Simple clock() benchmark ******************/
  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITmath(ui);
  end = clock();
  printf("math:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITclz(ui);
  end = clock();
  printf("clz:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITi2f(ui);
  end = clock();
  printf("i2f:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITasm(ui);
  end = clock();
  printf("asm:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITbitshift1(ui);
  end = clock();
  printf("bitshift1:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITbitshift2(ui);
  end = clock();
  printf("bitshift2\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  printf("\nThe lower, the better. Take note that a negative exponent is good! ;)\n");

  return EXIT_SUCCESS;
}

6

Muhtemelen bu yöntemi yalnızca mümkün olan en iyi performansı kesinlikle istersem kullanacak olsam da (örneğin, bitboard içeren bir tür tahta oyunu yapay zekası yazmak için), en etkili çözüm satır içi ASM kullanmaktır. Açıklamalı kod için bu blog gönderisinin Optimizasyonlar bölümüne bakın .

[...], bsrlmontaj talimatı en önemli bitin konumunu hesaplar. Bu nedenle, şu asmifadeyi kullanabiliriz :

asm ("bsrl %1, %0" 
     : "=r" (position) 
     : "r" (number));

Genişletmek için: standart döngü çözümü (sola kaydırma ve MSB'yi kontrol etme) muhtemelen en okunabilir olanıdır. Bit döndürmeyi içeren her durumda olduğu gibi, ASM'nin hızı yenilemez, ancak gerekli olmadıkça kodunuzu karıştırmanın bir anlamı yoktur. Hack'ler arada bir çözümdür - öyle ya da böyle gidin.
Noldorin

Logaritmayı almanın mükemmel okunabilir bir çözüm olacağını söyleyebilirim (derleyicinin bu asm talimatını kullanmak için onu optimize edip edemeyeceğini görmek için oluşturulan asm'yi kontrol edin)
jalf

Bazen satır içi ASM çözümü, CPU mikro kodundaki uygulamaya bağlı olarak daha yavaştır.
rlbond

5
@rlbound: Yanılsam da buna inanamıyorum. Herhangi bir modern işlemcide tek bir talimata çevrilebileceğini düşünebilirsiniz ....
Noldorin

3
@Noldorin biraz geç ama .. Tanım gereği tek bir talimat, ancak rlbond'un önerdiği gibi mikro kodluysa, o zaman bu tek talimat dahili olarak bir sürü µop'a kod çözebilir. Bu, AMD'nin mikro mimarilerinde ve Intel Atom'da geçerli olma eğilimindedir, ancak normal Intel mikro mimarilerinde bu, tamamen tek bir işlemdir.
harold

4

Bunu yapmak için bir rutine ihtiyacım vardı ve web'de arama yapmadan (ve bu sayfayı bulmadan) önce ikili aramaya dayalı kendi çözümümü buldum. Eminim ki birisi bunu daha önce yaptı! Sabit zamanda çalışır ve yayınlanan "aşikar" çözümden daha hızlı olabilir, ancak ben herhangi bir büyük iddiada bulunmuyorum, sadece ilgi için gönderiyorum.

int highest_bit(unsigned int a) {
  static const unsigned int maskv[] = { 0xffff, 0xff, 0xf, 0x3, 0x1 };
  const unsigned int *mask = maskv;
  int l, h;

  if (a == 0) return -1;

  l = 0;
  h = 32;

  do {
    int m = l + (h - l) / 2;

    if ((a >> m) != 0) l = m;
    else if ((a & (*mask << l)) != 0) h = m;

    mask++;
  } while (l < h - 1);

  return l;
}

4

bu bir tür ikili arama, her türlü (işaretsiz!) tamsayı türleriyle çalışır

#include <climits>
#define UINT (unsigned int)
#define UINT_BIT (CHAR_BIT*sizeof(UINT))

int msb(UINT x)
{
    if(0 == x)
        return -1;

    int c = 0;

    for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
    if(static_cast<UINT>(x >> i))
    {
        x >>= i;
        c |= i;
    }

    return c;
}

tamamlamak için:

#include <climits>
#define UINT unsigned int
#define UINT_BIT (CHAR_BIT*sizeof(UINT))

int lsb(UINT x)
{
    if(0 == x)
        return -1;

    int c = UINT_BIT-1;

    for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
    if(static_cast<UINT>(x << i))
    {
        x <<= i;
        c ^= i;
    }

    return c;
}

4
Lütfen ALL_CAPS'i typedefs veya aslında önişlemci makroları dışında herhangi bir şey için kullanmamayı düşünün . Bu, yaygın olarak kabul gören bir sözleşmedir.
underscore_d

4

Burada bazı aşırı karmaşık cevaplar. Debruin tekniği yalnızca girdi zaten ikinin bir gücü olduğunda kullanılmalıdır, aksi takdirde daha iyi bir yol vardır. 2 girişin gücü için Debruin, _BitScanReversetest ettiğim herhangi bir işlemciden bile daha hızlı, mutlak en hızlısı . Bununla birlikte, genel durumda, _BitScanReverse(veya derleyicinizde içsel ne denirse) en hızlısıdır (bazı CPU'larda mikro kodlanabilir).

İçsel işlev bir seçenek değilse, genel girdileri işlemek için en uygun yazılım çözümü burada verilmiştir.

u8  inline log2 (u32 val)  {
    u8  k = 0;
    if (val > 0x0000FFFFu) { val >>= 16; k  = 16; }
    if (val > 0x000000FFu) { val >>= 8;  k |= 8;  }
    if (val > 0x0000000Fu) { val >>= 4;  k |= 4;  }
    if (val > 0x00000003u) { val >>= 2;  k |= 2;  }
    k |= (val & 2) >> 1;
    return k;
}

Diğer cevapların çoğunun aksine, bu sürümün sonunda Debruin araması gerektirmediğini unutmayın. Konumu yerinde hesaplar.

Yine de tablolar tercih edilebilir, ancak tekrar tekrar çağırırsanız, önbellek kaçırma riski bir tablonun hızlanmasıyla gölgelenir.

u8 kTableLog2[256] = {
0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
};

u8 log2_table(u32 val)  {
    u8  k = 0;
    if (val > 0x0000FFFFuL) { val >>= 16; k  = 16; }
    if (val > 0x000000FFuL) { val >>=  8; k |=  8; }
    k |= kTableLog2[val]; // precompute the Log2 of the low byte

    return k;
}

Bu, burada verilen yazılım yanıtlarından herhangi biri arasında en yüksek verimi sağlamalıdır, ancak yalnızca ara sıra ararsanız, ilk kod parçam gibi masasız bir çözümü tercih edin.


1
Cevapların bazıları dalsızdır, ancak bu muhtemelen koşullu dallarla derlenecektir. Aynı değeri tekrar tekrar mı yoksa basit bir kalıp veya başka bir şeyle mi kıyasladınız? Şube yanlış tahmin, performans için bir katildir. stackoverflow.com/questions/11227809/…
Peter Cordes

3

Yukarıdaki cevapların işaret ettiği gibi, en önemli biti belirlemenin birkaç yolu vardır. Bununla birlikte, daha önce de belirtildiği gibi, yöntemler büyük olasılıkla 32bit veya 64bit kayıtlara özgüdür. Stanford.edu bithacks sayfa 32bit ve 64bit hem de iş bilgisayar olduğunu çözümler sunar. Küçük bir çalışma ile, MSB'yi elde etmek için sağlam bir çapraz mimari yaklaşım sağlamak üzere birleştirilebilirler. 64 ve 32 bit bilgisayarlarda derlenen / çalışan bulduğum çözüm şuydu:

#if defined(__LP64__) || defined(_LP64)
# define BUILD_64   1
#endif

#include <stdio.h>
#include <stdint.h>  /* for uint32_t */

/* CHAR_BIT  (or include limits.h) */
#ifndef CHAR_BIT
#define CHAR_BIT  8
#endif  /* CHAR_BIT */

/* 
 * Find the log base 2 of an integer with the MSB N set in O(N)
 * operations. (on 64bit & 32bit architectures)
 */
int
getmsb (uint32_t word)
{
    int r = 0;
    if (word < 1)
        return 0;
#ifdef BUILD_64
    union { uint32_t u[2]; double d; } t;  // temp
    t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] = 0x43300000;
    t.u[__FLOAT_WORD_ORDER!=LITTLE_ENDIAN] = word;
    t.d -= 4503599627370496.0;
    r = (t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] >> 20) - 0x3FF;
#else
    while (word >>= 1)
    {
        r++;
    }
#endif  /* BUILD_64 */
    return r;
}

İnt r değildi; başlangıçta #ifdef BUILD_64bayrağın üzerinde tanımlanmış mı? Bu durumda, koşullu içinde yeniden tanımlanması gerekmez.
David C. Rankin

3

Ardışık yaklaşımı kullanan C'deki bir versiyon:

unsigned int getMsb(unsigned int n)
{
  unsigned int msb  = sizeof(n) * 4;
  unsigned int step = msb;
  while (step > 1)
 {
    step /=2;
    if (n>>msb)
     msb += step;
   else
     msb -= step;
 }
  if (n>>msb)
    msb++;
  return (msb - 1);
}

Avantaj: Döngü sayısı her zaman aynı olduğundan, çalışma süresi verilen sayıdan bağımsız olarak sabittir. ("İşaretsiz int" kullanırken 4 döngü)


Üçlü bir operatör ( msb += (n>>msb) ? step : -step;) ile yazarsanız , daha fazla derleyicinin her adımda dallanma yanlış tahminlerinden kaçınarak dalsız asm oluşturması olasıdır ( stackoverflow.com/questions/11227809/… ).
Peter Cordes

3

Bu sorunun çok eski olduğunu biliyorum, ancak kendim bir msb () işlevi uyguladıktan sonra , burada ve diğer web sitelerinde sunulan çözümlerin çoğunun mutlaka en verimli olmadığını fark ettim - en azından kişisel verimlilik tanımım için (ayrıca bkz. Aşağıdaki Güncelleme ). İşte nedeni:

Çoğu çözüm (özellikle bir tür ikili arama şeması kullanan veya sağdan sola doğrusal tarama yapan naif yaklaşımı kullananlar), rastgele ikili sayılar için, çok uzun bir dizi ile başlayan pek çok sayı olmadığı gerçeğini ihmal ediyor gibi görünmektedir. sıfırlar. Aslında, herhangi bir bit genişliği için, tüm tam sayıların yarısı 1 ile başlar ve dörtte biri 01 ile başlar . Nereye geldiğimi gördün mü? Benim argümanım, en önemli bit konumundan en az önemli olana (soldan sağa) başlayan doğrusal bir taramanın , ilk bakışta göründüğü kadar "doğrusal" olmadığıdır.

1 gösterilebilir , herhangi bir bit genişliği için, test edilmesi gereken ortalama bit sayısı en fazla 2'dir. Bu , bit sayısına (!) Göre amortize edilmiş zaman karmaşıklığı O (1 ) anlamına gelir. .

Tabii ki, en kötü durum hala O (n) , ikili arama benzeri yaklaşımlarla elde ettiğiniz O (log (n)) ' den daha kötüdür , ancak en kötü durum çok az olduğu için çoğu uygulama için önemsizdir ( Güncelleme : tam olarak değil: Az sayıda olabilir, ancak yüksek olasılıkla gerçekleşebilirler - aşağıdaki Güncelleme bölümüne bakın).

İşte bulduğum "naif" yaklaşım, en azından benim makinemde diğer yaklaşımların çoğunu geride bırakıyor (32 bitlik girişler için ikili arama şemaları her zaman log 2 (32) = 5 adım gerektirir, oysa bu aptalca algoritma daha az gerektirir Ortalama olarak 2'den fazla) - bunun C ++ olması ve saf C olmaması için üzgünüz:

template <typename T>
auto msb(T n) -> int
{
    static_assert(std::is_integral<T>::value && !std::is_signed<T>::value,
        "msb<T>(): T must be an unsigned integral type.");

    for (T i = std::numeric_limits<T>::digits - 1, mask = 1 << i; i >= 0; --i, mask >>= 1)
    {
        if ((n & mask) != 0)
            return i;
    }

    return 0;
}

Güncelleme : Burada yazdıklarım, her bit kombinasyonunun eşit derecede olası olduğu keyfi tamsayılariçin tamamen doğru olsa da(benim hız testim, tüm 32 bitlik tamsayılariçin MSB'yi belirlemenin ne kadar sürdüğünü ölçtü), gerçek hayattaki tamsayılar için Bu tür bir işlevin çağrılacağı, genellikle farklı bir model izleyin: Örneğin, benim kodumda, bu işlev, bir nesne boyutunun 2'ninbir üssü olup olmadığını belirlemekveya 2'nin bir büyük veya eşit bir sonraki kuvvetini bulmak için kullanılır. nesne boyutu . Tahminimce, MSB'yi kullanan uygulamaların çoğu, bir tamsayının temsil edebileceği maksimum sayıdan çok daha küçük sayılar içerdiğidir (nesne boyutları nadiren bir size_t'deki tüm bitleri kullanır)). Bu durumda, benim çözümüm aslında ikili arama yaklaşımından daha kötü performans gösterecek - bu nedenle, çözümüm tüm tamsayılar arasında daha hızlı döngü oluşturacak olsa da muhtemelen ikincisi tercih edilmelidir .
TL; DR: Gerçek hayattaki tam sayılar muhtemelen bu basit algoritmanın en kötü durumuna karşı bir önyargıya sahip olacak ve bu , gerçekten keyfi tam sayılar için O (1) amorti edilmiş olmasına rağmen, sonunda daha kötü performans göstermesine neden olacak .

1 Argüman şu şekildedir (kaba taslak): Let n , bit sayısı (bit-genişliği) olsun. N bit ile temsil edilebilen toplam 2 n tam sayı vardır . Var 2 , n 1 - Bir başlayarak tam sayı 1 (ilk 1 kalan sabitlenir, - n 1 herhangi bir şey olabilir bit). Bu tam sayılar, MSB'yi belirlemek için döngünün yalnızca bir araya girmesini gerektirir. Bundan başka, var 2 2 - N başlayarak tamsayılardır 01 2 yineleme gerektiren, 2 - 3, n tamsayıları başlayarak 001 3 yineleme gerektiren, ve böyle devam eder.

Olası tüm tamsayılar için gerekli tüm yinelemeleri toplar ve bunları toplam tam sayı sayısı olan 2 n'ye bölersek , n bitlik tamsayılar için MSB'yi belirlemek için gereken ortalama yineleme sayısını elde ederiz :

(1 * 2 n - 1 + 2 * 2 n - 2 + 3 * 2 n - 3 + ... + n) / 2 n

Bu ortalama yineleme dizisi aslında yakınsaktır ve n için sonsuza doğru 2 sınırı vardır.

Bu nedenle, naif soldan sağa algoritma aslında herhangi bir sayıda bit için amortize edilmiş sabit zaman karmaşıklığına O (1) sahiptir .


2
Msb işlevlerinin girdilerinin eşit olarak dağıtılma eğiliminde olduğunun mutlaka adil bir varsayım olduğunu sanmıyorum. Pratikte, bu girdiler, kesme yazmaçları veya bitboardlar veya eşit olmayan şekilde dağıtılmış değerlere sahip başka veri yapıları olma eğilimindedir. Adil bir kıyaslama için çıktıların (girdilerin değil) eşit olarak dağıtılacağını varsaymanın daha güvenli olduğunu düşünüyorum.
johnwbyrd

3

bize verdi log2. Bu log2, bu sayfada gördüğünüz tüm özel sos uygulamalarına olan ihtiyacı ortadan kaldırır . Standardın log2uygulamasını şu şekilde kullanabilirsiniz :

const auto n = 13UL;
const auto Index = (unsigned long)log2(n);

printf("MSB is: %u\n", Index); // Prints 3 (zero offset)

Bir nait 0ULihtiyaçları, hem karşı çünkü korunması gerekiyor:

-∞ döndürülür ve FE_DIVBYZERO yükseltilir

Ben keyfi setleri o çekle bir örnek yazdım Indexetmek ULONG_MAXBuraya: https://ideone.com/u26vsi


için doğal sonucu ephemient en gcc tek yanıt ise:

const auto n = 13UL;
unsigned long Index;

_BitScanReverse(&Index, n);
printf("MSB is: %u\n", Index); // Prints 3 (zero offset)

_BitScanReverseDurumlar için belgelerIndex :

Bulunan ilk bitin (1) bit konumu ile yüklendi

Uygulamada, eğer nis 0ULthat Indexis set olduğunu0UL , tıpkı bir nof için olduğu gibi buldum 1UL. Fakat söz konusu belgelerde garantili tek şey nbir 0ULdönüş olmasıdır:

0 set biti bulunmazsa

Bu nedenle, tercih edilen log2uygulamaya benzer şekilde, geri dönüşün Indexbu durumda işaretli bir değere ayarlanması kontrol edilmelidir . Burada yine ULONG_MAXbu bayrak değeri için bir örnek yazdım : http://rextester.com/GCU61409


Hayır, yalnızca girdi ise _BitScanReverse0 döndürür . Bu gibi x86 en talimat setleri sadece giriş, çıkışını dayalı ZF. MS'in belgeleri hiçbir bit bulunmadığında ayarlanmadan bırakıldığını söylemesi ilginçtir ; x86 asm davranışıyla da eşleşir . (AMD bunu, src = 0'da hedef yazmacını değiştirilmemiş olarak bırakıyor olarak belgeliyor, ancak Intel, CPU'ları değiştirilmemiş bırakma davranışını uygulasa bile, yalnızca tanımsız çıktı diyor.) Bu, bulunamadı anlamına gelen x86'lardan farklıdır . 0BSRindex1bsrlzcnt32
Peter Cordes

@PeterCordes _BitScanReversesıfır tabanlı indeksleme kullanır, dolayısıyla n1 ise set bitinin indeksi aslında 0'dır. Ne yazık ki, dediğiniz gibi n0 ise çıktı da 0'dır :( Bu, n1 veya 0 arasında bir ayrım yapın. İletişim kurmaya çalıştığım şey buydu. Bunu söylemenin daha iyi bir yolu olduğunu düşünüyor musunuz?
Jonathan Mee

Sanırım nasıl geçtiğinden bahsediyorsun Index. Bu dönüş değeri değil . Girdi sıfır ise yanlış olan bir boole döndürür (ve bu nedenle Dizin normal olarak döndürülmek yerine referansla iletilir). godbolt.org/g/gQKJdE . Ve kontrol ettim: MS belgelerinin ifadelerine rağmen, _BitScanReverseİndeksi açık bırakmıyor n==0: sadece kayıt defterinde kullanılan değeri alırsınız. (Sizin durumunuzda muhtemelen Indexdaha sonra kullandığı kayıtla aynıdır , bu da size a'yı görmenize neden olur 0).
Peter Cordes

Bu soru c ++ olarak etiketlenmemiş.
technosaurus

@technosaurus Teşekkürler, kendimi unuttum. Sorunun log2C99'dan beri sahip olduğumuz C olduğu düşünüldüğünde .
Jonathan Mee

2

Bitsel operatörleri düşünün.

Soruyu ilk seferde yanlış anladım. En soldaki bit setiyle (diğerleri sıfır) bir int üretmelisiniz. Cmp'nin bu değere ayarlandığını varsayarsak:

position = sizeof(int)*8
while(!(n & cmp)){ 
   n <<=1;
   position--;
}

Bir dizgeye dönüştürmek ne demek? Ffs'nin tanımı bir int alır ve bir int döndürür. Dönüşüm nerede olur? Ve eğer bir kelimede bit ararsak, dönüştürme hangi amaca hizmet eder?
dreamlax

Bu işlevi bilmiyordum.
Vasil

8Olmalıdır CHAR_BIT. Bunun en hızlı yol olması pek olası değildir, çünkü tekrar tekrar aynı girdiyle kullanılmadığı sürece döngüden çıkıldığında dallanma yanlış tahmin olacaktır. Ayrıca, küçük girdiler için (çok sayıda sıfır), çok fazla döngü yapması gerekir. Bu, optimize edilmiş sürümlerle karşılaştırmak için bir birim testinde doğrulanması kolay sürüm olarak kullanacağınız geri dönüş yolu gibidir.
Peter Cordes

2

Josh'un kıyaslamasını genişletmek ... clz'yi aşağıdaki gibi geliştirebiliriz

/***************** clz2 ********************/

#define NUM_OF_HIGHESTBITclz2(a) ((a)                              \
                  ? (((1U) << (sizeof(unsigned)*8-1)) >> __builtin_clz(a)) \
                  : 0)

Asm ile ilgili olarak: bsr ve bsrl olduğunu unutmayın (bu "uzun" versiyondur). normal olan biraz daha hızlı olabilir.


1

Yapmaya çalıştığınız şeyin bir tamsayının log2 tamsayısını hesaplamak olduğuna dikkat edin,

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

unsigned int
Log2(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1; int k=0;
    for( step = 1; step < bits; ) {
        n |= (n >> step);
        step *= 2; ++k;
    }
    //printf("%ld %ld\n",x, (x - (n >> 1)) );
    return(x - (n >> 1));
}

Bir seferde 1 bitten fazlasını aramayı deneyebileceğinizi gözlemleyin.

unsigned int
Log2_a(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1;
    int step2 = 0;
    //observe that you can move 8 bits at a time, and there is a pattern...
    //if( x>1<<step2+8 ) { step2+=8;
        //if( x>1<<step2+8 ) { step2+=8;
            //if( x>1<<step2+8 ) { step2+=8;
            //}
        //}
    //}
    for( step2=0; x>1L<<step2+8; ) {
        step2+=8;
    }
    //printf("step2 %d\n",step2);
    for( step = 0; x>1L<<(step+step2); ) {
        step+=1;
        //printf("step %d\n",step+step2);
    }
    printf("log2(%ld) %d\n",x,step+step2);
    return(step+step2);
}

Bu yaklaşım bir ikili arama kullanır

unsigned int
Log2_b(unsigned long x)
{
    unsigned long n = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int hbit = bits-1;
    unsigned int lbit = 0;
    unsigned long guess = bits/2;
    int found = 0;

    while ( hbit-lbit>1 ) {
        //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        //when value between guess..lbit
        if( (x<=(1L<<guess)) ) {
           //printf("%ld < 1<<%d %ld\n",x,guess,1L<<guess);
            hbit=guess;
            guess=(hbit+lbit)/2;
            //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        }
        //when value between hbit..guess
        //else
        if( (x>(1L<<guess)) ) {
            //printf("%ld > 1<<%d %ld\n",x,guess,1L<<guess);
            lbit=guess;
            guess=(hbit+lbit)/2;
            //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        }
    }
    if( (x>(1L<<guess)) ) ++guess;
    printf("log2(x%ld)=r%d\n",x,guess);
    return(guess);
}

Başka bir ikili arama yöntemi, belki daha okunabilir,

unsigned int
Log2_c(unsigned long x)
{
    unsigned long v = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int step = bits;
    unsigned int res = 0;
    for( step = bits/2; step>0; )
    {
        //printf("log2(%ld) v %d >> step %d = %ld\n",x,v,step,v>>step);
        while ( v>>step ) {
            v>>=step;
            res+=step;
            //printf("log2(%ld) step %d res %d v>>step %ld\n",x,step,res,v);
        }
        step /= 2;
    }
    if( (x>(1L<<res)) ) ++res;
    printf("log2(x%ld)=r%ld\n",x,res);
    return(res);
}

Ve bunları test etmek isteyeceğiniz için,

int main()
{
    unsigned long int x = 3;
    for( x=2; x<1000000000; x*=2 ) {
        //printf("x %ld, x+1 %ld, log2(x+1) %d\n",x,x+1,Log2(x+1));
        printf("x %ld, x+1 %ld, log2_a(x+1) %d\n",x,x+1,Log2_a(x+1));
        printf("x %ld, x+1 %ld, log2_b(x+1) %d\n",x,x+1,Log2_b(x+1));
        printf("x %ld, x+1 %ld, log2_c(x+1) %d\n",x,x+1,Log2_c(x+1));
    }
    return(0);
}

1

Bunu "başka bir" yaklaşım olduğu için ortaya koymak, zaten verilmiş olanlardan farklı görünüyor.

aksi takdirde -1eğer döndürür (maks. sonuç 31)x==0floor( log2(x))

Problemi 32'den 4 bit'e düşürün, ardından bir tablo kullanın. Belki uygunsuz, ama pragmatik.

__builtin_clzTaşınabilirlik sorunları nedeniyle kullanmak istemediğimde bunu kullanıyorum .

Daha kompakt hale getirmek için, bunun yerine azaltmak için bir döngü kullanılabilir, her seferinde r'ye 4, en fazla 7 yineleme eklenebilir. Veya (64 bit için) gibi bazı hibritler: 8'e düşürmek için döngü, 4'e düşürmek için test edin.

int log2floor( unsigned x ){
   static const signed char wtab[16] = {-1,0,1,1, 2,2,2,2, 3,3,3,3,3,3,3,3};
   int r = 0;
   unsigned xk = x >> 16;
   if( xk != 0 ){
       r = 16;
       x = xk;
   }
   // x is 0 .. 0xFFFF
   xk = x >> 8;
   if( xk != 0){
       r += 8;
       x = xk;
   }
   // x is 0 .. 0xFF
   xk = x >> 4;
   if( xk != 0){
       r += 4;
       x = xk;
   }
   // now x is 0..15; x=0 only if originally zero.
   return r + wtab[x];
}

1

Woaw, bu birçok cevaptı. Eski bir soruyu cevapladığım için üzgün değilim.

int result = 0;//could be a char or int8_t instead
if(value){//this assumes the value is 64bit
    if(0xFFFFFFFF00000000&value){  value>>=(1<<5); result|=(1<<5);  }//if it is 32bit then remove this line
    if(0x00000000FFFF0000&value){  value>>=(1<<4); result|=(1<<4);  }//and remove the 32msb
    if(0x000000000000FF00&value){  value>>=(1<<3); result|=(1<<3);  }
    if(0x00000000000000F0&value){  value>>=(1<<2); result|=(1<<2);  }
    if(0x000000000000000C&value){  value>>=(1<<1); result|=(1<<1);  }
    if(0x0000000000000002&value){  result|=(1<<0);  }
}else{
  result=-1;
}

Bu cevap başka bir cevaba oldukça benziyor ... oh pekala.


Vardiya miktarlarını olduğu gibi yazmak 1<<khoş bir dokunuş. Ya maskeler? (1 << (1<<k-1)-1<< (1<<k-1)? ( most optimal? En üstün
ifadeyi

@greybeard Bu sorunun düzenlemelerine bakarsanız "optimal" kısmını eklediğimde göreceksiniz. Cevabımı değiştirirken onu kaldırmayı unuttum. Ayrıca ben bahsediyorsun neden emin değilim maskeleri? (Hangi maskeler? Seni takip etmiyorum)
Harry Svensson

( (bit) maskesi , bitleri seçici olarak seçmek / temizlemek için kullanılan / &ve içinde kullanılan değerlerdir &~.) Hex sabitlerini benzerleri ile değiştirebilirsiniz ((type)1<<(1<<k))-1<<(1<<k).
greybeard

Oh doğru, maske kullanıyorum, bunu tamamen unutmuşum. Bunu birkaç ay önce yanıtladım ... - Hmmm, derleme sırasında değerlendirildiği için hex değerlerine eşdeğer olduğunu söylüyorum . Bununla birlikte, biri şifreli ve diğeri onaltılıktır.
Harry Svensson

0

Kod:

    // x>=1;
    unsigned func(unsigned x) {
    double d = x ;
    int p= (*reinterpret_cast<long long*>(&d) >> 52) - 1023;
    printf( "The left-most non zero bit of %d is bit %d\n", x, p);
    }

Veya Y = 1 olarak ayarlayarak FPU komutunun FYL2X (Y * Log2 X) tamsayı kısmını alın


uhhhhh. ne? bu nasıl işliyor? herhangi bir şekilde taşınabilir mi?
underscore_d

Penceredeki kodlar taşınabilir. FYL2X () işlevi bir fpu talimatıdır, ancak taşınabilir ve bazı FPU / matematik kitaplıklarında bulunabilir.
jemin

@underscore_d Kayan nokta sayıları normalleştirildiği için işe yarar ... çift kaydırmaya dönüştürme öndeki sıfırları ortadan kaldırmak için mantis bitlerini çıkarır ve bu kod üsleri çıkarır ve kaydırılan bit sayısını belirlemek için ayarlar. Kesinlikle mimariden bağımsız değildir, ancak muhtemelen karşılaştığınız herhangi bir makinede çalışacaktır.
Jim Balter

Bu, bu cevabın alternatif bir versiyonudur , performans ve taşınabilirlik hakkındaki yorumlar için oraya bakın. (Spesifik olarak tip-punning için işaretçi dökümünün taşınabilir olmaması.) Adres matematiğini sadece yüksek 32 bitini yeniden yüklemek için kullanır double, bu muhtemelen başka bir şekilde tip-pun yerine depolar / yeniden yüklerse iyidir, örn. movqgibi bir talimatla buraya x86'da ulaşabilirsiniz.
Peter Cordes

Ayrıca , bu yöntemin (en azından) aralıktaki değerler için yanlış yanıt verdiğine dair korkunç bir uyarı sunduğum [bu yanıta yorum] 'a da dikkat edin [7FFFFFFFFFFFFE00 - 7FFFFFFFFFFFFFFF].
Glenn Slayden

0

Başka bir poster , bayt genişliğinde arama kullanarak bir arama tablosu sağladı . Eğer burada bir kullanan bir çözümdür (yerine sadece 256 arama girişlerinin bellek 32K pahasına) biraz daha performans eke istiyorum 15-bit arama tablosu içinde, C # 7 için .NET .

İlginç olan kısım tabloyu başlatmaktır. İşlemin ömrü boyunca istediğimiz görece küçük bir blok olduğu için bunun için de kullanarak yönetilmeyen bellek ayırıyorum Marshal.AllocHGlobal. Gördüğünüz gibi, maksimum performans için tüm örnek yerel olarak yazılmıştır:

readonly static byte[] msb_tab_15;

// Initialize a table of 32768 bytes with the bit position (counting from LSB=0)
// of the highest 'set' (non-zero) bit of its corresponding 16-bit index value.
// The table is compressed by half, so use (value >> 1) for indexing.
static MyStaticInit()
{
    var p = new byte[0x8000];

    for (byte n = 0; n < 16; n++)
        for (int c = (1 << n) >> 1, i = 0; i < c; i++)
            p[c + i] = n;

    msb_tab_15 = p;
}

Tablo, yukarıdaki kod aracılığıyla tek seferlik başlatma gerektirir. Salt okunurdur, bu nedenle eşzamanlı erişim için tek bir genel kopya paylaşılabilir. Bu tablo ile , çeşitli tam sayı genişlikleri (8, 16, 32 ve 64 bit) için burada aradığımız tamsayı günlüğü 2'ye hızlı bir şekilde bakabilirsiniz .

0'En yüksek ayarlı bit' kavramının tanımsız olduğu tek tam sayı olan tablo girişine değer verildiğine dikkat edin -1. Bu ayrım, aşağıdaki kodda bulunan 0 değerli üst kelimelerin düzgün işlenmesi için gereklidir. Daha fazla uzatmadan, çeşitli ilkel tam sayıların her birinin kodu:

ulong (64-bit) Sürüm

/// <summary> Index of the highest set bit in 'v', or -1 for value '0' </summary>
public static int HighestOne(this ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 0x40) - 1;      // handles cases v==0 and MSB==63

    int j = /**/ (int)((0xFFFFFFFFU - v /****/) >> 58) & 0x20;
    j |= /*****/ (int)((0x0000FFFFU - (v >> j)) >> 59) & 0x10;
    return j + msb_tab_15[v >> (j + 1)];
}

uint (32 bit) Sürümü

/// <summary> Index of the highest set bit in 'v', or -1 for value '0' </summary>
public static int HighestOne(uint v)
{
    if ((int)v <= 0)
        return (int)((v >> 26) & 0x20) - 1;     // handles cases v==0 and MSB==31

    int j = (int)((0x0000FFFFU - v) >> 27) & 0x10;
    return j + msb_tab_15[v >> (j + 1)];
}

Yukarıdakiler için çeşitli aşırı yüklemeler

public static int HighestOne(long v) => HighestOne((ulong)v);
public static int HighestOne(int v) => HighestOne((uint)v);
public static int HighestOne(ushort v) => msb_tab_15[v >> 1];
public static int HighestOne(short v) => msb_tab_15[(ushort)v >> 1];
public static int HighestOne(char ch) => msb_tab_15[ch >> 1];
public static int HighestOne(sbyte v) => msb_tab_15[(byte)v >> 1];
public static int HighestOne(byte v) => msb_tab_15[v >> 1];

Bu, özel bir performans testi donanımıyla karşılaştırdığım çok sayıda alternatif için .NET 4.7.2'de en iyi performansı temsil eden eksiksiz, çalışan bir çözümdür. Bunlardan bazıları aşağıda belirtilmiştir. Test parametreleri 65 bit pozisyonunun hepsinin tekdüze bir yoğunluğuydu, yani 0 ... 31/63 artı değer 0(-1 sonucunu verir). Hedef dizin konumunun altındaki bitler rastgele dolduruldu. Testler yalnızca x64 , sürüm modundaydı ve JIT optimizasyonları etkinleştirildi.




Buradaki resmi cevabımın sonu bu; Aşağıda, yukarıdaki kodun performansını ve doğruluğunu onaylamak için yaptığım testle ilişkili alternatif test adayları için kaynak koduna bazı sıradan notlar ve bağlantılar yer almaktadır.


Yukarıda sağlanan ve Tab16A olarak kodlanan sürüm, birçok çalışmada tutarlı bir galibiyetti. Aktif çalışma / sıfırdan formdaki bu çeşitli adaylar burada , burada ve burada bulunabilir .

 1 aday.HighestOne_Tab16A 622.496
 2 aday.HighestOne_Tab16C 628.234
 3 aday.HighestOne_Tab8A 649.146
 4 aday.HighestOne_Tab8B 656.847
 5 aday.HighestOne_Tab16B 657.147
 6 aday.HighestOne_Tab16D 659.650
 7 _highest_one_bit_UNMANAGED.HighestOne_U 702.900
 8 de_Bruijn.IndexOfMSB 709.672
 9 _old_2.HighestOne_Old2 715.810
10 _test_A.HighestOne8 757.188
11 _old_1.HighestOne_Old1 757.925
12 _test_A.HighestOne5 (güvensiz) 760.387
13 _test_B.HighestOne8 (güvenli değil) 763.904
14 _test_A.HighestOne3 (güvensiz) 766.433
15 _test_A.HighestOne1 (güvensiz) 767.321
16 _test_A.HighestOne4 (güvenli değil) 771.702
17 _test_B.HighestOne2 (güvensiz) 772.136
18 _test_B.HighestOne1 (güvensiz) 772.527
19 _test_B.HighestOne3 (güvensiz) 774.140
20 _test_A.HighestOne7 (güvensiz) 774.581
21 _test_B.HighestOne7 (güvensiz) 775.463
22 _test_A.HighestOne2 (güvenli değil) 776.865
23 aday.HighestOne_NoTab 777.698
24 _test_B.HighestOne6 (güvensiz) 779.481
25 _test_A.HighestOne6 (güvensiz) 781,553
26 _test_B.HighestOne4 (güvensiz) 785.504
27 _test_B.HighestOne5 (güvensiz) 789.797
28 _test_A.HighestOne0 (güvensiz) 809.566
29 _test_B.HighestOne0 (güvensiz) 814.990
30 _highest_one_bit.HighestOne 824.345
30 _bitarray_ext.RtlFindMostSignificantBit 894.069
31 aday En Yüksek Bire_ 898,865

ntdll.dll!RtlFindMostSignificantBitP / Invoke aracılığıyla yapılan korkunç performans şudur :

[DllImport("ntdll.dll"), SuppressUnmanagedCodeSecurity, SecuritySafeCritical]
public static extern int RtlFindMostSignificantBit(ulong ul);

Gerçekten çok kötü, çünkü işte tüm gerçek işlev:

    RtlFindMostSignificantBit:
        bsr rdx, rcx  
        mov eax,0FFFFFFFFh  
        movzx ecx, dl  
        cmovne      eax,ecx  
        ret

Bu beş satırdan kaynaklanan düşük performans hayal edemiyorum, bu yüzden yönetilen / yerel geçiş cezaları sorumlu olmalı. Ayrıca testin, 32KB (ve 64KB) short(16-bit) doğrudan arama tablolarını 128 bayt (ve 256 bayt) byte(8 bit) arama tablolarına göre gerçekten tercih etmesine de şaşırdım . Aşağıdakilerin 16 bitlik aramalarda daha rekabetçi olacağını düşündüm, ancak ikincisi sürekli olarak bundan daha iyi performans gösterdi:

public static int HighestOne_Tab8A(ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 64) - 1;

    int j;
    j =  /**/ (int)((0xFFFFFFFFU - v) >> 58) & 32;
    j += /**/ (int)((0x0000FFFFU - (v >> j)) >> 59) & 16;
    j += /**/ (int)((0x000000FFU - (v >> j)) >> 60) & 8;
    return j + msb_tab_8[v >> j];
}

İşaret edeceğim son şey, deBruijn yöntemimin daha iyi sonuç veremediği için oldukça şok olduğum. Bu, daha önce yaygın olarak kullandığım yöntem:

const ulong N_bsf64 = 0x07EDD5E59A4E28C2,
            N_bsr64 = 0x03F79D71B4CB0A89;

readonly public static sbyte[]
bsf64 =
{
    63,  0, 58,  1, 59, 47, 53,  2, 60, 39, 48, 27, 54, 33, 42,  3,
    61, 51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22,  4,
    62, 57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21,
    56, 45, 25, 31, 35, 16,  9, 12, 44, 24, 15,  8, 23,  7,  6,  5,
},
bsr64 =
{
     0, 47,  1, 56, 48, 27,  2, 60, 57, 49, 41, 37, 28, 16,  3, 61,
    54, 58, 35, 52, 50, 42, 21, 44, 38, 32, 29, 23, 17, 11,  4, 62,
    46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, 31, 22, 10, 45,
    25, 39, 14, 33, 19, 30,  9, 24, 13, 18,  8, 12,  7,  6,  5, 63,
};

public static int IndexOfLSB(ulong v) =>
    v != 0 ? bsf64[((v & (ulong)-(long)v) * N_bsf64) >> 58] : -1;

public static int IndexOfMSB(ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 64) - 1;

    v |= v >> 1; v |= v >> 2;  v |= v >> 4;   // does anybody know a better
    v |= v >> 8; v |= v >> 16; v |= v >> 32;  // way than these 12 ops?
    return bsr64[(v * N_bsr64) >> 58];
}

Bu SO sorusunda deBruijn yöntemlerinin ne kadar üstün ve harika olduğuna dair çok fazla tartışma var ve ben de aynı fikirde olma eğilimindeydim. Benim spekülasyonum, hem deBruijn hem de doğrudan arama tablosu yöntemlerinin (en hızlı bulduğum) bir tablo araması yapmak zorunda olmasına ve her ikisinin de çok az dallanmaya sahip olmasına rağmen, sadece deBruijn'in 64-bit çarpma işlemine sahip olmasıdır. Ben sadece buradaki IndexOfMSBfonksiyonları test ettim - deBruijn'i değil - IndexOfLSBama ikincisinin çok daha az işlem içerdiği için çok daha iyi şansa sahip olmasını bekliyorum (yukarıya bakın) ve muhtemelen LSB için kullanmaya devam edeceğim.


1
Modern x86 CPU'larda L1D önbelleği yalnızca 32kiB'dir. Aynı değerleri tekrar tekrar kullanmadığınız sürece büyük bir LUT, küçük bir LUT'den daha kötü olabilir. Değilseniz, sık sık önbellek kaçırırsınız.
Peter Cordes

0

Mütevazı yöntemim çok basit:

MSB (x) = INT [Günlük (x) / Günlük (2)]

Tercüme: X'in MSB'si (Temel x'in Günlüğü, Temel 2'nin Günlüğüne bölünür) tamsayı değeridir.

Bu, herhangi bir programlama diline kolayca ve hızlı bir şekilde uyarlanabilir. Çalıştığını kendiniz görmek için hesap makinenizde deneyin.


Bu, ilgilendiğiniz tek şey geliştirici verimliliği ise işe yarar. Çalışma zamanı verimliliği istiyorsanız, alternatif algoritmaya ihtiyacınız var.
Mikko Rantalainen

Bu, yuvarlama hatası nedeniyle başarısız olabilir. Örneğin, CPython 2 ve 3'te int(math.log((1 << 48) - 1) / math.log(2))48'dir.
benrg

0

İşte GCC ve Clang'da çalışan C için hızlı bir çözüm ; kopyalanmaya ve yapıştırılmaya hazır.

#include <limits.h>

unsigned int fls(const unsigned int value)
{
    return (unsigned int)1 << ((sizeof(unsigned int) * CHAR_BIT) - __builtin_clz(value) - 1);
}

unsigned long flsl(const unsigned long value)
{
    return (unsigned long)1 << ((sizeof(unsigned long) * CHAR_BIT) - __builtin_clzl(value) - 1);
}

unsigned long long flsll(const unsigned long long value)
{
    return (unsigned long long)1 << ((sizeof(unsigned long long) * CHAR_BIT) - __builtin_clzll(value) - 1);
}

Ve C ++ için biraz geliştirilmiş versiyonu .

#include <climits>

constexpr unsigned int fls(const unsigned int value)
{
    return (unsigned int)1 << ((sizeof(unsigned int) * CHAR_BIT) - __builtin_clz(value) - 1);
}

constexpr unsigned long fls(const unsigned long value)
{
    return (unsigned long)1 << ((sizeof(unsigned long) * CHAR_BIT) - __builtin_clzl(value) - 1);
}

constexpr unsigned long long fls(const unsigned long long value)
{
    return (unsigned long long)1 << ((sizeof(unsigned long long) * CHAR_BIT) - __builtin_clzll(value) - 1);
}

Kod valueolmayacağını varsayar 0. 0'a izin vermek istiyorsanız, onu değiştirmeniz gerekir.


0

Sorunuzun bir tam sayı (aşağıda v olarak adlandırılır) için olduğunu ve işaretsiz bir tam sayı olmadığını varsayıyorum.

int v = 612635685; // whatever value you wish

unsigned int get_msb(int v)
{
    int r = 31;                         // maximum number of iteration until integer has been totally left shifted out, considering that first bit is index 0. Also we could use (sizeof(int)) << 3 - 1 instead of 31 to make it work on any platform.

    while (!(v & 0x80000000) && r--) {   // mask of the highest bit
        v <<= 1;                        // multiply integer by 2.
    }
    return r;                           // will even return -1 if no bit was set, allowing error catch
}

İşareti hesaba katmadan çalışmasını istiyorsanız, fazladan bir 'v << = 1;' ekleyebilirsiniz. döngüden önce (ve buna göre r değerini 30 olarak değiştirin). Bir şey unutursam lütfen bana bildirin. Test etmedim ama gayet iyi çalışmalı.


v <<= 1olan tanımsız davranış (UB) olduğunda v < 0.
chux -

0x8000000, belki orada fazladan 0 demek istiyorsun.
MM
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.