Алгоритм для нахождения наименьшей степени, равной 2, большей или равной заданному значению

Мне нужно найти наименьшую степень, равную двум, что больше или равно заданному значению. Пока у меня есть это:

int value = 3221; // 3221 is just an example, could be any number
int result = 1;

while (result < value) result <<= 1;

Он отлично работает, но чувствует себя наивным. Есть ли лучший алгоритм для этой проблемы?

ИЗМЕНИТЬ. Были некоторые приятные ассемблерные предложения, поэтому я добавляю те теги к вопросу.

Ответ 1

Здесь мой любимый. Помимо первоначальной проверки того, является ли она недействительной (< 0, которую вы могли бы пропустить, если бы вы знали, что у вас есть только число = = 0), она не имеет циклов или условных выражений и, таким образом, будет превосходить большинство других методов. Это похоже на ответ erickson, но я думаю, что мой декрементинг x в начале и добавление 1 в конце немного менее неудобный, чем его ответ (а также избегает условного в конце).

/// Round up to next higher power of 2 (return x if it already a power
/// of 2).
inline int
pow2roundup (int x)
{
    if (x < 0)
        return 0;
    --x;
    x |= x >> 1;
    x |= x >> 2;
    x |= x >> 4;
    x |= x >> 8;
    x |= x >> 16;
    return x+1;
}

Ответ 3

В духе Quake II 0x5f3759df и версии IEEE для бит Twiddling Hacks это решение достигает удвоения, чтобы извлечь экспоненту в качестве средства вычисления уровня пола (lg2 ​​(n)). Это немного быстрее, чем принятое решение и намного быстрее, чем версия Bit Twiddling IEEE, поскольку она позволяет избежать математики с плавающей запятой. Как указано в кодировке, предполагается, что double является реальным поплавком * 8 IEEE на маленькой конечной машине.

int nextPow2(int n) 
{ 
    if ( n <= 1 ) return n;
    double d = n-1; 
    return 1 << ((((int*)&d)[1]>>20)-1022); 
} 

Изменить: добавить оптимизированную версию сборки x86 с помощью сотрудника. 4% -ное увеличение скорости, но все же примерно на 50% медленнее, чем версия bsr (6 секунд против 4 на моем ноутбуке для n = 1..2 ^ 31-2).

int nextPow2(int n) 
{ 
    if ( n <= 1 ) return n;
    double d;
    n--;
    __asm {
      fild    n 
      mov     eax,4
      fstp    d 
      mov     ecx, dword ptr d[eax]
      sar     ecx,14h 
      rol     eax,cl 
  }
} 

Ответ 4

На оборудовании Intel инструкция BSR близка к тому, что вы хотите - она ​​находит бит наиболее значимого набора. Если вам нужно быть более точным, вы можете задаться вопросом, являются ли оставшиеся бит точно нулевыми или нет. Я склонен предположить, что у другого процессора будет что-то вроде BSR - это вопрос, на который вы хотите ответить, чтобы нормализовать число. Если ваш номер больше 32 бит, вы можете выполнить сканирование с самого важного DWORD, чтобы найти первый бит DWORD с ANY битами. Edsger Dijkstra, скорее всего, замечает, что вышеупомянутые "алгоритмы" предполагают, что ваш компьютер использует двоичные разряды, в то время как из своего рода "алгоритмической" перспективы вы должны думать о машинах Тьюринга или что-то в этом роде, очевидно, я более прагматичный.

Ответ 5

Ваша реализация не наивна, она на самом деле логическая, за исключением того, что она неправильна - она ​​возвращает отрицание для чисел, большее, чем 1/2 максимального целочисленного размера.

Предполагая, что вы можете ограничить числа диапазоном от 0 до 2 ^ 30 (для 32-битных ints), он будет работать очень хорошо и намного быстрее, чем любые математические функции, связанные с логарифмами.

Unsigned ints будет работать лучше, но вы закончите бесконечный цикл (для чисел больше 2 ^ 31), так как вы никогда не сможете достичь 2 ^ 32 с < Оператор.

Ответ 6

Изучение возможных решений тесно связанной проблемы (то есть округление вниз, а не вверх), многие из которых значительно быстрее, чем наивный подход, доступно на "Бит Tweedling Hacks" , страница, отличный ресурс для тех видов оптимизации, которые вы ищете. Самое быстрое решение - использовать таблицу поиска с 256 записями, что уменьшает общее количество операций до 7, начиная с среднего значения 62 (по аналогичной методологии подсчета операций) для наивного подхода. Адаптация этих решений к вашей проблеме заключается в одном сравнении и приращении.

Ответ 7

Здесь находится шаблонная версия метода смещения бит.

template<typename T> T next_power2(T value)
{
    --value;
    for(size_t i = 1; i < sizeof(T) * CHAR_BIT; i*=2)
        value |= value >> i;
    return value+1;
}

Поскольку цикл использует только константы, он сглаживается компилятором. (Я проверил) Функция также является будущим доказательством.

Здесь используется тот, который использует __builtin_clz. (Также будущее доказательство)

template<typename T> T next_power2(T value)
{
    return 1 << ((sizeof(T) * CHAR_BIT) - __builtin_clz(value-1));
}

Ответ 8

pow (2, ceil (log2 (значение));

log2 (значение) = журнал (значение)/log (2);

Ответ 9

Как насчет рекурсивной версии шаблона для генерации константы компиляции:

template<uint32_t A, uint8_t B = 16>
struct Pow2RoundDown { enum{ value = Pow2RoundDown<(A | (A >> B)), B/2>::value }; };
template<uint32_t A>
struct Pow2RoundDown<A, 1> { enum{ value = (A | (A >> 1)) - ((A | (A >> 1)) >> 1) }; };

template<uint32_t A, uint8_t B = 16>
struct Pow2RoundUp { enum{ value = Pow2RoundUp<((B == 16 ? (A-1) : A) | ((B == 16 ? (A-1) : A) >> B)), B/2>::value }; };
template<uint32_t A >
struct Pow2RoundUp<A, 1> { enum{ value = ((A | (A >> 1)) + 1) }; };

Можно использовать так:

Pow2RoundDown<3221>::value, Pow2RoundUp<3221>::value

Ответ 10

Вы действительно не говорите, что вы подразумеваете под "лучшим алгоритмом", но поскольку тот, который вы представляете, совершенно ясен (если несколько испорчен), я предполагаю, что вы после более эффективного алгоритма.

Ларри Гритц дал, пожалуй, самый эффективный алгоритм c/С++ без накладных расходов таблицы поиска, и этого было бы достаточно в большинстве случаев (см. http://www.hackersdelight.org для аналогичных алгоритмов).

Как упоминалось ранее, большинство процессоров в наши дни имеют машинные инструкции для подсчета количества ведущих нулей (или эквивалентно возврата порога ms), однако их использование не переносится и - в большинстве случаев - не стоит усилий.

Однако большинство компиляторов имеют "внутренние" функции, которые позволяют использовать машинные инструкции, но более переносимым образом.

Microsoft С++ имеет _BitScanReverse(), а gcc обеспечивает __builtin_clz(), чтобы эффективно выполнять основную часть работы.

Ответ 11

Приведенный ниже код повторно разбивает младший бит до тех пор, пока номер не станет равным двум, а затем удвоит результат, если только число не будет состоять из двух. Преимущество состоит в том, что он работает в течение времени, пропорционального количеству установленных битов. К сожалению, у него есть недостаток, требующий больше инструкций почти во всех случаях, чем код в вопросе или предложения сборки. Я включаю его только для полноты.

int nextPow(int x) {
  int y = x
  while (x &= (x^(~x+1))) 
    y = x << 1;
  return y
}

Ответ 12

Я знаю, что это приманка с downvote, но если число достаточно мало (например, 8 или 16 бит), прямой поиск может быть самым быстрым.

// fill in the table
unsigned short tab[65536];
unsigned short bit = tab[i];

Возможно, его можно будет увеличить до 32 бит, сначала сделав высокое слово, а затем низкое.

//
unsigned long bitHigh = ((unsigned long)tab[(unsigned short)(i >> 16)]) << 16;
unsigned long bitLow = 0;
if (bitHigh == 0){
    bitLow = tab[(unsigned short)(i & 0xffff)];
}
unsigned long answer = bitHigh | bitLow;

Вероятно, не лучше, чем сдвиг или методы, но, возможно, может быть расширен до больших размеров слов.

(На самом деле это дает самый высокий 1-бит. Вам придется сдвинуть его налево, чтобы получить следующую более высокую мощность 2.)

Ответ 13

Моя версия:

int pwr2Test(size_t x) {
    return (x & (x - 1))? 0 : 1; 
}

size_t pwr2Floor(size_t x) {
    // A lookup table for rounding up 4 bit numbers to
    // the nearest power of 2.
    static const unsigned char pwr2lut[] = {
        0x00, 0x01, 0x02, 0x02,     //  0,  1,  2,  3
        0x04, 0x04, 0x04, 0x04,     //  4,  5,  6,  7
        0x08, 0x08, 0x08, 0x08,     //  8,  9, 10, 11
        0x08, 0x08, 0x08, 0x08      // 12, 13, 14, 15
    };

    size_t pwr2 = 0;                // The return value
    unsigned int i = 0;             // The nybble interator

    for( i = 0; x != 0; ++i ) {     // Iterate through nybbles
        pwr2 = pwr2lut[x & 0x0f];   // rounding up to powers of 2.
        x >>= 4;                    // (i - 1) will contain the
    }                               // highest non-zero nybble index.

    i = i? (i - 1) : i;
    pwr2 <<= (i * 4);
    return pwr2; 
}

size_t pwr2Size(size_t x) {
    if( pwr2Test(x) ) { return x; }
    return pwr2Floor(x) * 2; 
 }

Ответ 14

Мне нравится смена.

Я соглашусь на

    int bufferPow = 1;
    while ( bufferPow<bufferSize && bufferPow>0) bufferPow <<= 1;

таким образом цикл всегда завершается, а часть после && оценивается почти никогда. И я не думаю, что две строки стоят вызова функции. Также вы можете сделать длинный или короткий, в зависимости от вашего суждения, и это очень читаемо. (если bufferPow становится отрицательным, надеюсь, ваш основной код быстро выйдет.)

Обычно вы вычисляете 2-мощь только один раз в начале алгоритма, поэтому оптимизация в любом случае будет глупой. Однако было бы интересно, если бы кто-то достаточно скучал, он бы позаботился о конкурсе скорости... используя приведенные выше примеры и 255 256 257.. 4195 4196 4197

Ответ 15

Любая функция журнала может быть преобразована в базу блогов 2 путем деления на журнал 2:

$ /usr/local/pypy-1.9/bin/pypy
Python 2.7.2 (341e1e3821ff, Jun 07 2012, 15:38:48)
[PyPy 1.9.0 with GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
And now for something completely different: ``<arigato> yes but there is not
much sense if I explain all about today greatest idea if tomorrow it's
completely outdated''
>>>> import math
>>>> print math.log(65535)/math.log(2)
15.9999779861
>>>> print math.log(65536)/math.log(2)
16.0
>>>>

Это, конечно, не будет на 100% точным, так как имеется арифметика с плавающей запятой.

Ответ 16

Это работает и очень быстро (на моем 64-разрядном процессоре Intel Core 2 Duo с частотой 2,66 ГГц).

#include <iostream>
int main(void) {
    int testinput,counter;
    std::cin >> testinput;
    while (testinput > 1) {
        testinput = testinput >> 1;
        counter++;
    }
    int finalnum = testinput << counter+1;
    printf("Is %i\n",finalnum);
    return 0;
}

Я тестировал его на 3, 6 и 65496, и были даны правильные ответы (4, 8 и 65536).

Извините, если это кажется немного тайным, я был под влиянием пары часов Doom перед тем, как писать.:)