Apa cara tercepat / paling efisien untuk menemukan set bit (msb) tertinggi dalam bilangan bulat di C?

119

Jika saya memiliki beberapa bilangan bulat n, dan saya ingin mengetahui posisi bit paling signifikan (yaitu, jika bit paling tidak signifikan ada di sebelah kanan, saya ingin mengetahui posisi bit kiri terjauh yaitu 1), apa metode tercepat / paling efisien untuk mencari tahu?

Saya tahu bahwa POSIX mendukung ffs()metode di strings.h untuk menemukan bit set pertama, tetapi tampaknya tidak ada fls()metode yang sesuai .

Apakah ada cara yang sangat jelas untuk melakukan ini yang saya lewatkan?

Bagaimana jika Anda tidak dapat menggunakan fungsi POSIX untuk portabilitas?

Sunting: Bagaimana dengan solusi yang bekerja pada arsitektur 32 dan 64 bit (banyak dari daftar kode sepertinya hanya bekerja pada 32 bit int).

Zxaos
sumber
ada beberapa implementasi di sini: graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightLinear (Sunting: Setelah membaca ulang pertanyaan Anda, saya menyadari bahwa tautan di atas adalah untuk menemukan bit set paling kanan, bukan yang paling kiri seperti yang Anda butuhkan, meskipun tanpa rasa ukuran kata, itu sulit untuk dijawab)
spender
2
Lihat " Jumlah algoritma nol di depan" di Hacker's Delight .
Darius Bacon
Itu menghitung nol di sebelah kanan ; pertanyaannya adalah tentang angka nol di kiri. Setidaknya, dalam sekejap saya tidak melihatnya di sana.
Darius Bacon
2
apakah Anda secara khusus menginginkan nomor bit 'n', atau apakah 2 ^ n sudah cukup?
Alnitak
1
Lihat algoritme "Log Base 2" - seperti yang dikatakan Anderson dalam artikel: "Log base 2 dari bilangan bulat sama dengan posisi kumpulan bit tertinggi (atau kumpulan bit paling signifikan, MSB)"
Michael Burr

Jawaban:

64

GCC memiliki :

 - Fungsi Bawaan: int __builtin_clz (unsigned int x)
     Mengembalikan jumlah 0-bit di depan X, mulai dari paling banyak
     posisi bit yang signifikan. Jika X adalah 0, hasilnya tidak terdefinisi.

 - Fungsi Bawaan: int __builtin_clzl (panjang tak bertanda tangan)
     Mirip dengan `__builtin_clz ', kecuali tipe argumennya adalah` unsigned
     panjang'.

 - Fungsi Bawaan: int __builtin_clzll (panjang tak bertanda tangan)
     Mirip dengan `__builtin_clz ', kecuali tipe argumennya adalah` unsigned
     Panjang panjang'.

Saya berharap mereka diterjemahkan menjadi sesuatu yang cukup efisien untuk platform Anda saat ini, apakah itu salah satu algoritma bit-twiddling yang mewah, atau instruksi tunggal.


Sebuah trik berguna jika masukan Anda dapat menjadi nol adalah __builtin_clz(x | 1): tanpa syarat pengaturan bit rendah tanpa memodifikasi setiap orang lain membuat output 31untuk x=0, tanpa mengubah output untuk input lain.

Untuk menghindari keharusan melakukan itu, opsi Anda yang lain adalah intrinsik khusus platform seperti ARM GCC __clz(tidak perlu header), atau x86 _lzcnt_u32pada CPU yang mendukung lzcntinstruksi. (Berhati-hatilah karena lzcntmen - decode seperti bsrpada CPU yang lebih lama daripada melakukan kesalahan, yang memberikan 31-lzcnt untuk input bukan-nol.)

Sayangnya tidak ada cara untuk mengambil keuntungan dari berbagai instruksi CLZ pada platform non-x86 yang menentukan hasil untuk input = 0 sebagai 32 atau 64 (sesuai dengan lebar operan). x86 juga lzcntmelakukannya, sambil bsrmenghasilkan indeks-bit yang harus dibalik kompilator kecuali Anda menggunakannya 31-__builtin_clz(x).

(The "undefined result" bukanlah C Undefined Behavior, hanya sebuah nilai yang tidak ditentukan. Sebenarnya apapun yang ada di register tujuan saat instruksi dijalankan. AMD mendokumentasikannya, Intel tidak, tapi CPU Intel mengimplementasikan perilaku itu . Tapi itu bukan apa pun yang sebelumnya ada di variabel C yang Anda tetapkan, itu biasanya bukan cara kerja ketika gcc mengubah C menjadi asm. Lihat juga Mengapa memecah "ketergantungan keluaran" dari LZCNT penting? )

efemient
sumber
5
MSVC akan mengadakan _BitScanReverse
ratchet freak
1
Perilaku undefined-on-zero memungkinkan mereka mengompilasi ke satu instruksi BSR pada x86, bahkan ketika LZCNT tidak tersedia. Ini adalah keuntungan besar untuk __builtin_ctzover ffs, yang mengkompilasi ke BSF dan CMOV untuk menangani kasus input-was-zero. Pada arsitektur tanpa implementasi yang cukup singkat (misalnya ARM lama tanpa clzinstruksi), gcc memancarkan panggilan ke fungsi pembantu libgcc.
Peter Cordes
41

Dengan asumsi Anda menggunakan x86 dan bermain untuk sedikit assembler inline, Intel menyediakan BSRinstruksi ("bit scan reverse"). Ini cepat di beberapa x86 (dikodekan di mikro pada orang lain). Dari manual:

Mencari operan sumber untuk set bit yang paling signifikan (1 bit). Jika 1 bit paling signifikan ditemukan, indeks bitnya disimpan di operan tujuan. Operand sumber dapat berupa register atau lokasi memori; operan tujuan adalah register. Indeks bit adalah offset unsigned dari bit 0 dari operan sumber. Jika operand sumber konten adalah 0, konten operand tujuan tidak ditentukan.

(Jika Anda menggunakan PowerPC, ada cntlzinstruksi serupa ("hitung nol di depan").)

Kode contoh untuk gcc:

#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;
}

Lihat juga tutorial assembler sebaris ini , yang menunjukkan (bagian 9.4) itu jauh lebih cepat daripada kode perulangan.

timday
sumber
4
Sebenarnya instruksi ini biasanya dikodekan menjadi loop dan agak lambat.
rlbond
2
Yang mana ? BSR atau CNTLZ? Saat saya membaca x86-timing.pdf yang direferensikan di atas, BSR hanya lambat di Netburst Pentiums. Saya tidak tahu apa-apa tentang PowerPC.
timday
5
... OK, pada pemeriksaan lebih dekat buatlah bahwa "BSR hanya cepat pada P3 / Pentium-M / Core2 x86s". Lambat pada Netburst dan AMD.
timday
1
Perlu diketahui: Dua tautan terakhir Anda sudah mati.
Baum mit Augen
2
@rlbond: ya, BSR di P4 Prescott adalah 2 uops dengan 16 siklus latensi (!), dengan satu per keluaran 4c. Namun pada Netburst sebelumnya, hanya latensi 4 siklus (masih 2 uops), dan satu per 2c throughput. (sumber: agner.org/optimize ). Pada kebanyakan CPU, ia juga memiliki ketergantungan pada keluarannya yang tidak diperhitungkan oleh gcc (bila masukannya nol, perilaku sebenarnya adalah membiarkan tujuan tidak berubah). Hal ini dapat menyebabkan masalah seperti stackoverflow.com/questions/25078285/… . IDK mengapa gcc melewatkan BSR saat memperbaikinya.
Peter Cordes
38

Karena 2 ^ N adalah bilangan bulat dengan hanya himpunan bit ke-N (1 << N), mencari posisi (N) dari bit himpunan tertinggi adalah basis log bilangan bulat 2 dari bilangan bulat itu.

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

unsigned int v;
unsigned r = 0;

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

Algoritme yang "jelas" ini mungkin tidak transparan untuk semua orang, tetapi ketika Anda menyadari bahwa kode bergeser ke kanan satu bit berulang kali hingga bit paling kiri telah dialihkan (perhatikan bahwa C memperlakukan nilai bukan nol sebagai true) dan mengembalikan angka tersebut pergeseran, itu masuk akal. Ini juga berarti bahwa ia bekerja bahkan ketika lebih dari satu bit disetel - hasilnya selalu untuk bit paling signifikan.

Jika Anda menggulir ke bawah pada halaman itu, ada variasi yang lebih cepat dan lebih kompleks. Namun, jika Anda tahu Anda berurusan dengan angka dengan banyak nol di depan, pendekatan naif dapat memberikan kecepatan yang dapat diterima, karena pergeseran bit agak cepat di C, dan algoritme sederhana tidak memerlukan pengindeksan array.

CATATAN: Saat menggunakan nilai 64-bit, berhati-hatilah saat menggunakan algoritma yang sangat pintar; banyak dari mereka hanya bekerja dengan benar untuk nilai 32-bit.

Quinn Taylor
sumber
2
@Johan Melangkah melalui debugger dapat membantu menjelaskan mengapa loop keluar. Pada dasarnya, its 'karena ekspresi dalam kondisi bernilai 0 (yang dianggap salah) setelah 1 bit terakhir digeser ke kanan.
Quinn Taylor
2
Ide bagus untuk menggunakan hasil akhir seperti itu :)
Johan
6
catatan: harus tidak bertanda tangan, untuk bilangan bulat bertanda tangan pergeseran kanan gagal untuk bilangan negatif.
Xantix
2
Xantix: Pergeseran di C / C ++ adalah pergeseran logis, jadi ini berfungsi dengan baik. Untuk Java, JavaScript, atau D, Anda perlu menggunakan operator shift logis >>>. Ditambah mungkin pembanding != 0, dan beberapa jumlah tanda kurung yang tidak ditentukan.
Mengejar
8
@ Chase: Tidak, tidak. Ini pergeseran logis untuk unsigned . Untuk ditandatangani , itu mungkin atau mungkin bukan pergeseran logis (dan biasanya aritmatika, sebenarnya).
Tim Čas
17

Ini harus secepat kilat:

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];
}
Tokoh utama
sumber
25
7 pergeseran bit, 5 atau instruksi, kelipatan dan potensi cache miss. :) Apakah Anda melakukan benchmark, atau melihat assembler yang dihasilkan? Ini bisa berakhir sangat lambat, tergantung pada seberapa banyak kompiler dapat menghilangkannya.
jalf
5
Saya baru disini. Saya tidak mendapatkan suara negatif guys. Saya telah memberikan satu-satunya jawaban dengan kode sumber yang benar-benar berfungsi.
Protagonis
9
"Kemungkinan cache hilang" mungkin karena kode ini memerlukan akses ke tabel pencariannya. Jika tabel itu tidak di-cache saat dipanggil, akan ada penghentian saat diambil. Ini mungkin membuat kinerja kasus terburuk jauh lebih buruk daripada solusi yang tidak menggunakan LUT.
bersantai
13
bukan itu intinya. Ini menggunakan lebih banyak cache data daripada yang diperlukan (lebih dari satu baris cache, bahkan), dan lebih banyak cache instruksi daripada yang diperlukan. Anda mungkin akan mendapatkan cache miss yang bisa dihindari saat pertama kali Anda memanggil fungsi, dan ini akan mengotori cache lebih dari yang diperlukan, jadi setelah panggilan, kode lain mungkin mengalami lebih banyak kesalahan daripada yang diperlukan. LUT sering kali tidak sebanding dengan masalahnya karena cache miss mahal. Tapi saya hanya mengatakan itu adalah sesuatu yang ingin saya tolak sebelum saya mengklaim itu "secepat kilat". Tidak bahwa itu adalah pasti masalah.
jalf
6
Tabel ini memiliki 32 entri, dan setiap nilainya <255 (127), jadi tentukan tabel sebagai tipe unsigned char, dan itu akan muat dalam satu baris cache L1 32 byte. Dan semuanya cocok dalam dua baris cache.
ChuckCottrill
16

Ini seperti menemukan semacam log integer. Ada trik yang sedikit memutarbalikkan, tetapi saya telah membuat alat sendiri untuk ini. Tujuannya tentu saja untuk kecepatan.

Kesadaran saya adalah bahwa CPU sudah memiliki detektor bit otomatis, digunakan untuk konversi integer ke float! Jadi gunakan itu.

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

Versi ini mentransmisikan nilai menjadi dua kali lipat, lalu membaca eksponen, yang memberi tahu Anda di mana bit itu berada. Pergeseran dan pengurangan mewah adalah mengekstrak bagian yang tepat dari nilai IEEE.

Ini sedikit lebih cepat untuk menggunakan pelampung, tetapi pelampung hanya dapat memberi Anda posisi 24 bit pertama karena presisi yang lebih kecil.


Untuk melakukan ini dengan aman, tanpa perilaku tidak terdefinisi di C ++ atau C, gunakan memcpyalih-alih casting pointer untuk jenis-punning. Penyusun tahu cara menyebariskannya secara efisien.

// 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;

Atau di C99 dan yang lebih baru, gunakan file union {double d; uint32_t u[2];};. Namun perhatikan bahwa di C ++, punning tipe gabungan hanya didukung pada beberapa kompiler sebagai ekstensi, bukan di ISO C ++.


Ini biasanya akan lebih lambat daripada intrinsik khusus platform untuk instruksi penghitungan nol terdepan, tetapi ISO C portabel tidak memiliki fungsi seperti itu. Beberapa CPU juga tidak memiliki instruksi penghitungan nol di depan, tetapi beberapa di antaranya dapat secara efisien mengonversi bilangan bulat menjadi double. Jenis-punning pola bit FP kembali ke integer bisa lambat, meskipun (misalnya pada PowerPC itu membutuhkan penyimpanan / reload dan biasanya menyebabkan macet-hit-store).

Algoritme ini berpotensi berguna untuk implementasi SIMD, karena lebih sedikit CPU yang memiliki SIMD lzcnt. x86 hanya mendapat instruksi seperti itu dengan AVX512CD

SPWorley
sumber
2
Iya. Dan gcc akan melakukan hal-hal buruk dengan kode seperti ini dengan -O2 karena pengoptimalan tipe-aliasing.
MSN
4
casting antara integer dan floating point bisa sangat mahal pada x86 CPU
jalf
1
Ya, biaya FPU-nya tinggi. Tetapi pengukuran waktu aktual menunjukkan ini lebih cepat daripada operasi semua-bit atau terutama loop apa pun. Cobalah dan ambil yang tercepat selalu merupakan saran terbaik. Saya tidak punya masalah dengan GCC dan -O2 dengan ini.
SPWorley
1
Bukankah ini perilaku tidak terdefinisi (membaca nilai melalui penunjuk dari tipe yang tidak kompatibel)?
dreamlax
3
Hacker's Delight menjelaskan cara mengoreksi kesalahan dalam float 32-bit dalam 5-3 Menghitung Leading 0. Berikut kode mereka, yang menggunakan penyatuan anonim untuk tumpang tindih asFloat dan asInt: k = k & ~ (k >> 1); asFloat = (float) k + 0,5f; n = 158 - (asInt >> 23); (dan ya, ini bergantung pada perilaku yang ditentukan implementasi)
D Coetzee
11

Kaz Kylheku di sini

Saya membandingkan dua pendekatan untuk angka lebih dari 63 bit ini (tipe panjang panjang di gcc x86_64), menjauh dari bit tanda.

(Saya kebetulan membutuhkan "temukan bit tertinggi" ini untuk sesuatu, Anda tahu.)

Saya menerapkan pencarian biner berbasis data (berdasarkan salah satu jawaban di atas). Saya juga menerapkan pohon keputusan yang sepenuhnya tidak digulung dengan tangan, yang hanya kode dengan operan langsung. Tanpa loop, tidak ada tabel.

Pohon keputusan (tertinggi_bit_unrolled) diukur menjadi 69% lebih cepat, kecuali untuk kasus n = 0 di mana pencarian biner memiliki pengujian eksplisit.

Pengujian khusus pencarian biner untuk kasus 0 hanya 48% lebih cepat daripada pohon keputusan, yang tidak memiliki pengujian khusus.

Kompiler, mesin: (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;
}

Program tes cepat dan kotor:

#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;
}

Dengan hanya menggunakan -O2, perbedaannya menjadi lebih besar. Pohon keputusan hampir empat kali lebih cepat.

Saya juga membandingkan dengan kode pergeseran bit yang naif:

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

Ini hanya cepat untuk jumlah kecil, seperti yang diharapkan. Dalam menentukan bahwa bit tertinggi adalah 1 untuk n == 1, ia melakukan benchmark lebih dari 80% lebih cepat. Namun, setengah dari angka yang dipilih secara acak dalam ruang 63 bit memiliki kumpulan bit ke-63!

Pada input 0x3FFFFFFFFFFFFFFFF, versi pohon keputusan agak lebih cepat daripada versi 1, dan menunjukkan 1120% lebih cepat (12,2 kali) daripada bit shifter.

Saya juga akan membandingkan pohon keputusan dengan GCC bawaan, dan juga mencoba campuran masukan daripada mengulang dengan nomor yang sama. Mungkin ada beberapa prediksi cabang yang sedang berlangsung dan mungkin beberapa skenario caching yang tidak realistis yang membuatnya lebih cepat secara artifisial pada pengulangan.

Kaz
sumber
9
Saya tidak mengatakan ini tidak baik, tetapi program pengujian Anda di sini hanya menguji pada nomor yang sama, yang setelah 2-3 iterasi akan menetapkan prediktor cabang ke posisi akhirnya dan setelah itu mereka akan membuat prediksi cabang yang sempurna. Hal baiknya adalah bahwa dengan distribusi acak total, setengah angka akan mendekati prediksi sempurna, yaitu bit63.
Surt
8

Bagaimana dengan

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

?

Marco Amagliani
sumber
Ini adalah versi lambat (tetapi lebih portabel) dari jawaban ini , yang menjelaskan mengapa ini berhasil.
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 register, 13 instruksi. Percaya atau tidak, ini biasanya lebih cepat daripada instruksi BSR yang disebutkan di atas, yang beroperasi dalam waktu linier. Ini adalah waktu logaritmik.

Dari http://aggregate.org/MAGIC/#Most%20Significant%201%20Bit

rlbond.dll
sumber
7
Kode diatas tidak menjawab pertanyaan tersebut. Ini mengembalikan integer unsigned di mana bit paling signifikan di x tetap aktif dan semua bit lainnya dimatikan. Pertanyaannya adalah mengembalikan posisi paling signifikan pada bit.
Protagonis
3
Anda kemudian dapat menggunakan pendekatan urutan De Bruijn untuk menemukan indeks bit yang ditetapkan. :-)
R .. GitHub STOP HELPING ICE
5
@Protagonist, katanya dalam komentar yang sudah cukup.
rlbond
Yang ini (dari halaman yang sama) akan melakukan apa yang Anda butuhkan, tetapi membutuhkan fungsi tambahan. aggregate.org/MAGIC/#Log2%20of%20an%20Integer
Quinn Taylor
1
BSR cepat pada CPU Intel sejak Core2 setidaknya. LZCNT cepat pada CPU AMD, dan gcc menggunakannya __builtin_clzjika diaktifkan dengan -march=nativeatau sesuatu (karena cepat pada setiap CPU yang mendukungnya). Bahkan pada CPU seperti AMD Bulldozer-family di mana BSR "lambat", ini tidak terlalu lambat: 7 m-op dengan latensi 4 siklus dan satu per 4c throughput. Di Atom, BSR sangat lambat: 16 siklus. Di Silvermont, ini 10 uops dengan 10 siklus latensi. Ini mungkin latensi sedikit lebih rendah daripada BSR di Silvermont, tapi IDK.
Peter Cordes
6

Berikut adalah beberapa tolok ukur (sederhana), dari algoritma yang saat ini diberikan di halaman ini ...

Algoritma belum diuji pada semua masukan dari unsigned int; jadi periksa dulu, sebelum menggunakan sesuatu secara membabi buta;)

Di mesin saya, clz (__builtin_clz) dan asm bekerja paling baik. asm tampaknya lebih cepat dari clz ... tetapi mungkin karena patokan sederhana ...

//////// 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;
}
Josh
sumber
6

Meskipun saya mungkin hanya akan menggunakan metode ini jika saya benar-benar membutuhkan kinerja terbaik (misalnya untuk menulis semacam AI permainan papan yang melibatkan bitboards), solusi paling efisien adalah menggunakan ASM sebaris. Lihat bagian Pengoptimalan pada entri blog ini untuk kode dengan penjelasan.

[...], bsrlinstruksi perakitan menghitung posisi bit yang paling signifikan. Jadi, kita bisa menggunakan asmpernyataan ini :

asm ("bsrl %1, %0" 
     : "=r" (position) 
     : "r" (number));
Noldorin
sumber
Untuk memperluas: solusi loop standar (menggeser ke kiri dan memeriksa MSB) mungkin yang paling mudah dibaca. Seperti dalam semua kasus yang melibatkan bit twiddling, kecepatan ASM tidak dapat dikalahkan, meskipun tidak ada gunanya mengacaukan kode Anda kecuali diperlukan. Peretasan adalah solusi di antara - pergi dengan satu atau lain cara.
Noldorin
Saya akan mengatakan mengambil logaritma akan menjadi solusi yang dapat dibaca dengan sempurna (periksa asm yang dihasilkan untuk melihat apakah kompiler dapat mengoptimalkannya untuk menggunakan instruksi asm ini)
jalf
Terkadang solusi ASM sebaris lebih lambat, bergantung pada penerapan dalam kode mikro CPU.
rlbond
5
@rlbound: Saya hampir tidak percaya itu, meskipun saya mungkin salah. Pada CPU modern mana pun orang akan berpikir bahwa itu akan diterjemahkan ke satu instruksi ....
Noldorin
3
@Noldorin agak terlambat tapi .. Ini menurut definisi instruksi tunggal, tetapi jika itu dikodekan sebagai rlbond menyarankan maka instruksi tunggal itu dapat mendekode ke sejumlah besar µops secara internal. Itu cenderung menjadi kasus pada mikroarsitektur AMD, dan Intel Atom, tetapi pada mikroarsitektur Intel normal itu adalah operasi tunggal sepenuhnya.
Harold
4

Saya memiliki kebutuhan akan rutinitas untuk melakukan ini dan sebelum mencari web (dan menemukan halaman ini) saya datang dengan solusi saya sendiri berdasarkan pencarian biner. Meskipun saya yakin seseorang telah melakukan ini sebelumnya! Ini berjalan dalam waktu yang konstan dan bisa lebih cepat daripada solusi "jelas" yang diposting, meskipun saya tidak membuat klaim yang bagus, hanya mempostingnya untuk kepentingan.

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;
}
rumah gantung
sumber
4

itu semacam pencarian biner, ini bekerja dengan semua jenis tipe integer (unsigned!)

#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;
}

untuk melengkapi:

#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;
}

sumber
4
Harap pertimbangkan untuk tidak menggunakan ALL_CAPS untuk typedefs atau memang apa pun kecuali makro praprosesor. Ini adalah konvensi yang diterima secara luas.
underscore_d
4

Beberapa jawaban yang terlalu rumit di sini. Teknik Debruin hanya boleh digunakan ketika input sudah menjadi kekuatan dua, jika tidak, ada cara yang lebih baik. Untuk kekuatan 2 input, Debruin adalah yang tercepat mutlak, bahkan lebih cepat daripada _BitScanReverseprosesor mana pun yang saya uji. Namun, dalam kasus umum, _BitScanReverse(atau apa pun yang disebut intrinsik dalam kompiler Anda) adalah yang tercepat (pada CPU tertentu itu dapat di-microcode).

Jika fungsi intrinsik bukan pilihan, berikut adalah solusi perangkat lunak yang optimal untuk memproses input umum.

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;
}

Perhatikan bahwa versi ini tidak memerlukan pencarian Debruin di bagian akhir, tidak seperti kebanyakan jawaban lainnya. Ini menghitung posisi di tempat.

Tabel bisa lebih disukai meskipun, jika Anda memanggilnya berulang kali cukup sering, risiko cache miss dikalahkan oleh percepatan tabel.

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;
}

Ini akan menghasilkan throughput tertinggi dari semua jawaban perangkat lunak yang diberikan di sini, tetapi jika Anda hanya memanggilnya sesekali, lebih suka solusi tanpa tabel seperti cuplikan pertama saya.

VoidStar
sumber
1
Beberapa jawaban tidak memiliki cabang, tetapi ini mungkin akan dikompilasi dengan cabang bersyarat. Apakah Anda hanya melakukan benchmark dengan nilai yang sama berulang kali, atau pola sederhana atau semacamnya? Salah prediksi cabang adalah pembunuh kinerja. stackoverflow.com/questions/11227809/…
Peter Cordes
3

Seperti yang ditunjukkan oleh jawaban di atas, ada sejumlah cara untuk menentukan bit yang paling signifikan. Namun, seperti yang juga ditunjukkan, metode ini cenderung unik untuk register 32bit atau 64bit. The Halaman bithacks stanford.edu menyediakan solusi yang bekerja untuk 32bit dan 64bit komputasi. Dengan sedikit kerja, mereka dapat digabungkan untuk memberikan pendekatan lintas arsitektur yang solid untuk mendapatkan MSB. Solusi yang saya temukan yang dikompilasi / bekerja di komputer 64 & 32 bit adalah:

#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;
}
David C. Rankin
sumber
Bukankah int r; awalnya didefinisikan di atas #ifdef BUILD_64bendera? Dalam hal ini tidak perlu redefinisi dalam kondisional.
David C. Rankin
3

Versi di C menggunakan perkiraan berurutan:

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);
}

Keuntungan: waktu berjalan konstan terlepas dari jumlah yang diberikan, karena jumlah loop selalu sama. (4 loop saat menggunakan "unsigned int")


sumber
Jika Anda menulisnya dengan operator ternary ( msb += (n>>msb) ? step : -step;), lebih banyak kompiler cenderung membuat asm tanpa cabang, menghindari kesalahan prediksi cabang pada setiap langkah ( stackoverflow.com/questions/11227809/… ).
Peter Cordes
3

Saya tahu pertanyaan ini sangat tua, tetapi baru saja menerapkan fungsi msb () sendiri, saya menemukan bahwa sebagian besar solusi yang disajikan di sini dan di situs web lain belum tentu yang paling efisien - setidaknya untuk definisi efisiensi pribadi saya (lihat juga Pembaruan di bawah ). Inilah alasannya:

Sebagian besar solusi (terutama yang menggunakan skema pencarian biner atau pendekatan naif yang melakukan pemindaian linier dari kanan ke kiri) tampaknya mengabaikan fakta bahwa untuk bilangan biner arbitrer, tidak banyak yang dimulai dengan urutan yang sangat panjang. nol. Faktanya, untuk lebar bit apa pun, setengah dari semua bilangan bulat dimulai dengan 1 dan seperempatnya dimulai dengan 01 . Lihat kemana tujuanku? Argumen saya adalah bahwa pemindaian linier mulai dari posisi bit yang paling signifikan hingga yang paling tidak signifikan (kiri ke kanan) tidak begitu "linier" seperti yang terlihat pada pandangan pertama.

Dapat ditunjukkan 1 , bahwa untuk setiap lebar bit, jumlah rata-rata bit yang perlu diuji paling banyak 2. Ini diterjemahkan menjadi kompleksitas waktu diamortisasi dari O (1) sehubungan dengan jumlah bit (!) .

Tentu saja, kasus terburuk masih O (n) , lebih buruk daripada O (log (n)) yang Anda dapatkan dengan pendekatan mirip-pencarian biner, tetapi karena ada begitu sedikit kasus terburuk, mereka dapat diabaikan untuk sebagian besar aplikasi ( Perbarui : tidak cukup: Mungkin ada sedikit, tetapi mungkin terjadi dengan probabilitas tinggi - lihat Pembaruan di bawah).

Berikut adalah pendekatan "naif" yang saya buat, yang setidaknya di mesin saya mengalahkan sebagian besar pendekatan lain (skema pencarian biner untuk int 32-bit selalu memerlukan log 2 (32) = 5 langkah, sedangkan algoritme konyol ini membutuhkan lebih sedikit dari rata-rata 2) - maaf karena ini C ++ dan bukan C murni:

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;
}

Pembaruan : Sementara apa yang saya tulis di sini sangat benar untukbilangan bulat sewenang - wenang , di mana setiap kombinasi bit sama-sama mungkin (tes kecepatan saya hanya mengukur berapa lama waktu yang dibutuhkan untuk menentukan MSB untuk semua bilangan bulat 32-bit), bilangan bulat kehidupan nyata, untuk dimana fungsi seperti itu akan dipanggil, biasanya mengikuti pola yang berbeda: Dalam kode saya, misalnya, fungsi ini digunakan untuk menentukan apakah ukuran objek adalah pangkat 2, atau untuk menemukan pangkat 2 berikutnya lebih besar atau sama dari ukuran objek . Dugaan saya adalah bahwa sebagian besar aplikasi yang menggunakan MSB melibatkan angka yang jauh lebih kecil daripada angka maksimum yang dapat diwakili oleh integer (ukuran objek jarang menggunakan semua bit dalam size_t). Dalam kasus ini, solusi saya sebenarnya akan bekerja lebih buruk daripada pendekatan pencarian biner - jadi yang terakhir mungkin lebih disukai, meskipun solusi saya akan lebih cepat mengulang melalui semua bilangan bulat.
TL; DR: Bilangan bulat kehidupan nyata mungkin akan memiliki bias terhadap kasus terburuk dari algoritma sederhana ini, yang pada akhirnya akan membuatnya berkinerja lebih buruk - terlepas dari kenyataan bahwa itu diamortisasi O (1) untuk bilangan bulat yang benar-benar sewenang-wenang.

1 Argumennya seperti ini (draf kasar): Misalkan n adalah jumlah bit (lebar bit). Ada total 2 n bilangan bulat yang dapat direpresentasikan dengan n bit. Ada 2 n - 1 bilangan bulat yang dimulai dengan 1 ( 1 pertama tetap, sisa n - 1 bit bisa apa saja). Integer tersebut hanya membutuhkan satu interasi loop untuk menentukan MSB. Selanjutnya, ada 2 n - 2 bilangan bulat dimulai dengan 01 , membutuhkan 2 iterasi, 2 n - 3 bilangan bulat dimulai dengan 001 , membutuhkan 3 iterasi, dan seterusnya.

Jika kita menjumlahkan semua iterasi yang diperlukan untuk semua kemungkinan bilangan bulat dan membaginya dengan 2 n , jumlah total bilangan bulat, kita mendapatkan jumlah rata-rata iterasi yang diperlukan untuk menentukan MSB untuk bilangan bulat n- bit:

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

Rangkaian iterasi rata-rata ini sebenarnya konvergen dan memiliki batas 2 untuk n menuju tak terhingga

Dengan demikian, algoritma kiri-ke-kanan naif sebenarnya memiliki kompleksitas waktu konstan diamortisasi dari O (1) untuk sejumlah bit.

Finnegan
sumber
2
Saya tidak berpikir itu adalah asumsi yang adil bahwa input ke fungsi msb cenderung didistribusikan secara merata. Dalam praktiknya, input ini cenderung berupa register interupsi atau papan bit atau beberapa struktur data lain dengan nilai yang tidak terdistribusi secara merata. Untuk patokan yang adil, saya pikir lebih aman untuk mengasumsikan bahwa output (bukan input) akan didistribusikan secara merata.
johnwbyrd
3

telah memberi kami log2. Ini menghilangkan kebutuhan untuk semua log2penerapan saus khusus yang Anda lihat di halaman ini. Anda dapat menggunakan log2implementasi standar seperti ini:

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

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

Sebuah ndari 0ULkebutuhan untuk dijaga terhadap juga, karena:

-∞ dikembalikan dan FE_DIVBYZERO dimunculkan

Saya telah menulis sebuah contoh dengan cek bahwa set sewenang-wenang Indexuntuk ULONG_MAXsini: https://ideone.com/u26vsi


Itu akibat wajar dari jawaban gcc ephemient adalah:

const auto n = 13UL;
unsigned long Index;

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

Dokumentasi untuk_BitScanReverse negara bagian yaitu Index:

Dimuat dengan posisi bit dari set pertama bit (1) yang ditemukan

Dalam prakteknya saya telah menemukan bahwa jika nadalah 0ULyang Indexdiatur untuk0UL , seperti itu akan untuk ndari 1UL. Tetapi satu-satunya hal yang dijamin dalam dokumentasi dalam kasus ndari 0ULadalah bahwa pengembaliannya adalah:

0 jika tidak ada bit set yang ditemukan

Jadi, serupa dengan log2implementasi yang lebih disukai di atas, kembalian harus diperiksa pengaturannya Indexke nilai yang ditandai dalam kasus ini. Saya sekali lagi menulis contoh penggunaan ULONG_MAXuntuk nilai bendera ini di sini: http://rextester.com/GCU61409

Jonathan Mee
sumber
Tidak, _BitScanReversemengembalikan 0 hanya jika masukannya 0. Ini seperti instruksi x86BSR , yang menyetel ZF hanya berdasarkan input, bukan output. Menarik bahwa MS mengatakan dokumen indextidak disetel saat tidak ada 1bit yang ditemukan; yang juga cocok dengan perilaku asm x86 bsr. (AMD mendokumentasikannya sebagai membiarkan register tujuan tidak dimodifikasi pada src = 0, tetapi Intel hanya mengatakan keluaran yang tidak ditentukan meskipun CPU mereka menerapkan perilaku biarkan-tidak dimodifikasi.) Ini tidak seperti x86 lzcnt, yang memberikan 32untuk tidak ditemukan.
Peter Cordes
@PeterCordes _BitScanReversemenggunakan pengindeksan berbasis nol, jadi jika n1 maka indeks dari bit yang disetel ternyata 0. Sayangnya, seperti yang Anda katakan jika n0 maka outputnya juga 0 :( Ini berarti tidak ada cara untuk menggunakan kembali ke membedakan antara n1 atau 0. Itulah yang saya coba komunikasikan. Apakah menurut Anda ada cara yang lebih baik untuk mengatakan ini?
Jonathan Mee
Saya pikir Anda sedang berbicara tentang cara mengaturnya Index. Itu bukan nilai pengembaliannya . Ini mengembalikan boolean yang salah jika inputnya nol (dan inilah mengapa Indeks diteruskan oleh referensi alih-alih dikembalikan secara normal). godbolt.org/g/gQKJdE . Dan saya memeriksa: meskipun kata-kata dalam dokumen MS, _BitScanReversetidak membiarkan Indeks tidak disetel n==0: Anda hanya mendapatkan nilai apa pun di register yang kebetulan digunakannya. (Yang dalam kasus Anda mungkin adalah register yang sama dengan yang digunakan Indexsetelahnya, sehingga Anda melihat a 0).
Peter Cordes
Pertanyaan ini tidak diberi tag c ++.
technosaurus
@technosaurus Terima kasih, saya lupa diri. Mengingat bahwa pertanyaannya adalah C yang sebenarnya kita miliki log2sejak C99.
Jonathan Mee
2

Pikirkan operator bitwise.

Saya salah paham pertanyaan pertama kali. Anda harus menghasilkan int dengan bit set paling kiri (yang lain nol). Dengan asumsi cmp disetel ke nilai itu:

position = sizeof(int)*8
while(!(n & cmp)){ 
   n <<=1;
   position--;
}
Vasil
sumber
Apa maksud Anda mengubah menjadi string? Definisi dari ffs menggunakan int dan mengembalikan sebuah int. Dimana konversinya? Dan apa tujuan konversi ini jika kita mencari sedikit kata?
dreamlax
Saya tidak tahu fungsi itu.
Vasil
The 8harus CHAR_BIT. Ini sangat tidak mungkin menjadi cara tercepat, karena kesalahan prediksi cabang akan terjadi saat keluar dari loop kecuali ini digunakan dengan input yang sama berulang kali. Selain itu, untuk input kecil (banyak nol), ia harus melakukan banyak loop. Ini seperti cara fallback yang Anda gunakan sebagai versi yang mudah diverifikasi dalam pengujian unit untuk dibandingkan dengan versi yang dioptimalkan.
Peter Cordes
2

Memperluas patokan Josh ... seseorang dapat meningkatkan clz sebagai berikut

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

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

Mengenai asm: perhatikan bahwa ada bsr dan bsrl (ini adalah versi "panjang"). yang normal mungkin sedikit lebih cepat.

JonesD
sumber
1

Perhatikan bahwa apa yang Anda coba lakukan adalah menghitung log2 integer dari sebuah integer,

#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));
}

Perhatikan bahwa Anda dapat mencoba mencari lebih dari 1 bit dalam satu waktu.

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);
}

Pendekatan ini menggunakan pencarian biner

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);
}

Metode pencarian biner lain, mungkin lebih mudah dibaca,

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);
}

Dan karena Anda ingin menguji ini,

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);
}
ChuckCottrill
sumber
1

Menempatkan ini karena ini adalah pendekatan 'yang lain', tampaknya berbeda dari yang lain yang sudah diberikan.

mengembalikan -1jika x==0, sebaliknya floor( log2(x)) (hasil maksimal 31)

Kurangi dari masalah 32 menjadi 4 bit, lalu gunakan tabel. Mungkin janggal, tapi pragmatis.

Inilah yang saya gunakan ketika saya tidak ingin menggunakan __builtin_clzkarena masalah portabilitas.

Untuk membuatnya lebih kompak, seseorang dapat menggunakan loop untuk mengurangi, menambahkan 4 ke r setiap kali, maks 7 iterasi. Atau beberapa hybrid, seperti (untuk 64 bit): loop untuk dikurangi menjadi 8, uji untuk mengurangi menjadi 4.

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];
}
greggo
sumber
1

Wah, itu banyak sekali jawaban. Saya tidak menyesal menjawab pertanyaan lama.

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;
}

Jawaban ini sangat mirip dengan jawaban lain ... oh baiklah.

Harry Svensson
sumber
Menulis jumlah shift 1<<kadalah sentuhan yang bagus. Bagaimana dengan topengnya? (1 << (1<<k-1)-1<< (1<<k-1)? ( most optimal? Anda membandingkan superlatif?)
greybeard
@greybeard Jika Anda melihat hasil edit pertanyaan ini, Anda akan melihat ketika saya menambahkan bagian "optimal". Saya lupa untuk menghapusnya saat saya mengubah jawaban saya. Juga saya tidak yakin mengapa Anda berbicara tentang itu topeng? (Masker apa? Saya tidak mengikuti Anda)
Harry Svensson
( (bit) mask adalah nilai yang digunakan untuk memilih / membersihkan bit secara selektif / digunakan dalam &dan &~.) Anda dapat mengganti konstanta hex dengan cara ((type)1<<(1<<k))-1<<(1<<k).
greybeard
Oh ya, saya menggunakan topeng, saya benar-benar lupa tentang itu. Saya menjawab ini beberapa bulan yang lalu ... - Hmmm, karena itu dievaluasi selama waktu kompilasi, saya katakan itu setara dengan nilai hex. Namun, satu samar dan satu heksadesimal.
Harry Svensson
0

Kode:

    // 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);
    }

Atau dapatkan bagian integer dari instruksi FPU FYL2X (Y * Log2 X) dengan mengatur Y = 1

jemin
sumber
uhhhhh. apa? bagaimana fungsinya? apakah ini portabel?
underscore_d
Kode di jendela bersifat portabel. Fungsi FYL2X () adalah instruksi fpu, tetapi mungkin porting dan dapat ditemukan di beberapa perpustakaan FPU / matematika.
jemin
@underscore_d Ini berfungsi karena bilangan floating point dinormalisasi ... mengubah menjadi menggeser ganda bit mantissa untuk menghilangkan nol di depan, dan kode ini mengekstrak eksponen dan menyesuaikannya untuk menentukan jumlah bit yang digeser. Ini tentu saja tidak bergantung pada arsitektur, tetapi mungkin akan berfungsi pada mesin apa pun yang Anda temui.
Jim Balter
Ini adalah versi alternatif dari jawaban ini , lihat di sana untuk komentar tentang kinerja dan portabilitas. (Khususnya non-portabilitas casting pointer untuk jenis-punning.) Ini menggunakan matematika alamat untuk hanya memuat ulang 32 bit tinggi double, yang mungkin bagus jika itu benar-benar menyimpan / memuat ulang daripada jenis-pun dengan cara lain, misalnya dengan movqinstruksi seperti yang mungkin Anda dapatkan di sini pada x86.
Peter Cordes
Perhatikan juga [komentar untuk jawaban itu] saya, di mana saya menawarkan peringatan mengerikan bahwa metode ini memberikan jawaban yang salah untuk nilai-nilai dalam (setidaknya) kisaran [7FFFFFFFFFFFFE00 - 7FFFFFFFFFFFFFFF].
Glenn Slayden
0

Poster lain menyediakan tabel pencarian menggunakan pencarian lebar byte . Jika Anda ingin menambah kinerja (dengan biaya memori 32K daripada hanya 256 entri pencarian) berikut adalah solusi menggunakan tabel pencarian 15-bit , di C # 7 untuk .NET .

Bagian yang menarik adalah menginisialisasi tabel. Karena ini adalah blok yang relatif kecil yang kami inginkan selama masa proses, saya mengalokasikan memori yang tidak terkelola untuk ini dengan menggunakan Marshal.AllocHGlobal. Seperti yang Anda lihat, untuk performa maksimal, seluruh contoh ditulis sebagai native:

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;
}

Tabel membutuhkan inisialisasi satu kali melalui kode di atas. Ini hanya-baca sehingga satu salinan global dapat dibagikan untuk akses bersamaan. Dengan tabel ini, Anda dapat dengan cepat mencari log bilangan bulat 2 , yang kita cari di sini, untuk semua lebar bilangan bulat yang bervariasi (8, 16, 32, dan 64 bit).

Perhatikan bahwa entri tabel untuk 0, satu-satunya bilangan bulat yang gagasan 'set bit tertinggi' tidak ditentukan, diberi nilai -1. Pembedaan ini diperlukan untuk penanganan yang tepat atas kata-kata atas bernilai 0 pada kode di bawah ini. Tanpa basa-basi lagi, berikut adalah kode untuk masing-masing primitif integer:

ulong (64-bit) Versi

/// <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)];
}

Versi uint (32-bit)

/// <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)];
}

Berbagai kelebihan beban di atas

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];

Ini adalah solusi kerja lengkap yang mewakili kinerja terbaik pada .NET 4.7.2 untuk banyak alternatif yang saya bandingkan dengan harness uji kinerja khusus. Beberapa di antaranya disebutkan di bawah ini. Parameter uji adalah kerapatan seragam dari semua posisi 65 bit, yaitu, nilai 0 ... 31/63 plus 0(yang menghasilkan hasil -1). Bit di bawah posisi indeks target diisi secara acak. Pengujiannya hanya x64 , mode rilis, dengan pengoptimalan JIT diaktifkan.




Itulah akhir dari jawaban formal saya di sini; berikut ini adalah beberapa catatan santai dan tautan ke kode sumber untuk kandidat tes alternatif yang terkait dengan pengujian yang saya jalankan untuk memvalidasi kinerja dan kebenaran kode di atas.


Versi yang disediakan di atas, dikodekan sebagai Tab16A adalah pemenang yang konsisten atas banyak proses. Berbagai kandidat ini, dalam bentuk kerja / awal aktif, dapat ditemukan di sini , di sini , dan di sini .

 1 kandidat.HighestOne_Tab16A 622.496
 2 calon.HighestOne_Tab16C 628.234
 3 kandidat.HighestOne_Tab8A 649.146
 4 kandidat.HighestOne_Tab8B 656.847
 5 calon.HighestOne_Tab16B 657.147
 6 kandidat.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 (tidak aman) 760.387
13 _test_B.HighestOne8 (tidak aman) 763.904
14 _test_A.HighestOne3 (tidak aman) 766.433
15 _test_A.HighestOne1 (tidak aman) 767.321
16 _test_A.HighestOne4 (tidak aman) 771.702
17 _test_B.HighestOne2 (tidak aman) 772.136
18 _test_B.HighestOne1 (tidak aman) 772.527
19 _test_B.HighestOne3 (tidak aman) 774.140
20 _test_A.HighestOne7 (tidak aman) 774.581
21 _test_B.HighestOne7 (tidak aman) 775.463
22 _test_A.HighestOne2 (tidak aman) 776.865
23 kandidat.HighestOne_NoTab 777.698
24 _test_B.HighestOne6 (tidak aman) 779.481
25 _test_A.HighestOne6 (tidak aman) 781.553
26 _test_B.HighestOne4 (tidak aman) 785,504
27 _test_B.HighestOne5 (tidak aman) 789.797
28 _test_A.HighestOne0 (tidak aman) 809.566
29 _test_B.HighestOne0 (tidak aman) 814.990
30 _highest_one_bit.HighestOne 824.345
30 _bitarray_ext.RtlFindMostSignificantBit 894.069
31 kandidat. HighestOne_Naive 898.865

Yang perlu diperhatikan adalah kinerja mengerikan ntdll.dll!RtlFindMostSignificantBitvia P / Invoke:

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

Ini sangat buruk, karena inilah seluruh fungsi sebenarnya:

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

Saya tidak bisa membayangkan kinerja buruk yang berasal dari lima baris ini, jadi hukuman transisi yang dikelola / asli harus disalahkan. Saya juga terkejut bahwa pengujian tersebut benar-benar menyukai shorttabel pencarian langsung 32KB (dan 64KB) (16-bit) daripada tabel pencarian 128-byte (dan 256-byte) byte(8-bit). Saya pikir yang berikut akan lebih kompetitif dengan pencarian 16-bit, tetapi yang terakhir secara konsisten mengungguli ini:

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];
}

Hal terakhir yang akan saya tunjukkan adalah saya cukup terkejut bahwa metode deBruijn saya tidak berjalan lebih baik. Ini adalah metode yang sebelumnya saya gunakan secara luas:

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];
}

Ada banyak diskusi tentang bagaimana metode deBruijn yang superior dan hebat pada pertanyaan SO ini , dan saya cenderung setuju. Spekulasi saya adalah, meskipun metode tabel deBruijn dan tabel pencarian langsung (yang menurut saya paling cepat) keduanya harus melakukan pencarian tabel, dan keduanya memiliki percabangan yang sangat minimal, hanya deBruijn yang memiliki operasi penggandaan 64-bit. Saya hanya menguji IndexOfMSBfungsinya di sini - bukan deBruijn --tetapi IndexOfLSBsaya berharap deBruijn memiliki peluang yang jauh lebih baik karena memiliki lebih sedikit operasi (lihat di atas), dan saya kemungkinan akan terus menggunakannya untuk LSB.

Glenn Slayden
sumber
1
Cache L1D pada CPU x86 modern hanya 32kiB. LUT besar cenderung lebih buruk daripada LUT kecil kecuali Anda menggunakan nilai yang sama berulang kali. Jika tidak, Anda akan sering kehilangan cache.
Peter Cordes
0

Metode saya yang sederhana sangat sederhana:

MSB (x) = INT [Log (x) / Log (2)]

Terjemahan: MSB dari x adalah nilai integer (Log dari Base x dibagi dengan Log dari Base 2).

Ini dapat dengan mudah dan cepat disesuaikan dengan bahasa pemrograman apa pun. Cobalah di kalkulator Anda untuk melihat sendiri bahwa ini berfungsi.

SpartanWar
sumber
Itu berfungsi jika yang Anda minati hanyalah efisiensi pengembang. Jika Anda menginginkan efisiensi waktu proses, Anda memerlukan algoritme alternatif.
Mikko Rantalainen
Ini bisa gagal karena kesalahan pembulatan. Misalnya, di CPython 2 dan 3, int(math.log((1 << 48) - 1) / math.log(2))adalah 48.
benrg
0

Berikut adalah solusi cepat untuk C yang berfungsi di GCC dan Clang ; siap untuk disalin dan ditempel.

#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);
}

Dan versi yang sedikit ditingkatkan untuk C ++ .

#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);
}

Kode berasumsi bahwa valueitu tidak akan terjadi 0. Jika Anda ingin memperbolehkan 0, Anda perlu mengubahnya.

TANPA NAMA
sumber
0

Saya berasumsi pertanyaan Anda adalah untuk integer (disebut v di bawah) dan bukan integer unsigned.

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
}

Jika Anda ingin membuatnya bekerja tanpa memperhitungkan tanda, Anda dapat menambahkan 'v << = 1;' ekstra sebelum loop (dan ubah nilai r menjadi 30 sesuai). Tolong beritahu saya jika saya lupa sesuatu. Saya belum mengujinya tetapi seharusnya berfungsi dengan baik.

Antonin GAVREL
sumber
v <<= 1adalah perilaku tidak terdefinisi (UB) saat v < 0.
chux - Pulihkan Monica
0x8000000, mungkin maksud Anda tambahan 0 di sana.
MM