то, что мне нужно, - это то, во что я могу ввести число, и оно вернет бит наивысшего порядка. Я уверен, что есть простой способ. Ниже приведен пример вывода (слева - ввод)
1 -> 1 2 -> 2 3 -> 2 4 -> 4 5 -> 4 6 -> 4 7 -> 4 8 -> 8 9 -> 8 ... 63 -> 32





На ум приходит постоянное удаление младшего бита ...
int highest_order_bit( int x )
{
int y = x;
do {
x = y;
y = x & (x-1); //remove low order bit
}
while( y != 0 );
return x;
}
Это должно помочь.
int hob (int num)
{
if (!num)
return 0;
int ret = 1;
while (num >>= 1)
ret <<= 1;
return ret;
}
hob (1234) возвращает 1024
.
hob (1024) возвращает 1024
.
hob (1023) возвращает 512
Чтобы он работал с нулями, просто добавьте: while (num >> = 1 && num)
На самом деле вам нужно, чтобы проверить на ноль. Поскольку ret начинается с 1 и растет, этот алгоритм не заставит его достичь 0, ответ на ввод 0.
Мой ответ лучше. Никаких петель!
хороший компилятор развернет циклы для повышения производительности. Если он действительно работает быстрее без цикла.
Для GCC лучшей функцией является __builtin_clz (v) - она возвращает количество ведущих (двоичных) нулей, и, следовательно, вы можете получить самую значимую битовую позицию с помощью 32-clz (num) (или 64-clzll (num) для 64 бит)
Из «Восторга хакера»:
int hibit(unsigned int n) {
n |= (n >> 1);
n |= (n >> 2);
n |= (n >> 4);
n |= (n >> 8);
n |= (n >> 16);
return n - (n >> 1);
}
Эта версия предназначена для 32-битных int, но логика может быть расширена для 64-битных или выше.
разве не должно быть более эффективным использование XOR в последней строке? п ^ (п >> 1)
@alveko, хотя XOR может быть реализован быстрее, чем вычитание на уровне транзистора / затвора, они будут занимать одинаковое количество тактовых циклов на большинстве современных процессоров
меньше транзисторов, больше КПД, меньше тепла, больше циклов :)
Для интуиции это "расширяет" старший бит вправо, так что 0b100010 становится 0b111111, затем сдвигается вправо на 0b011111 и вычитает это, давая 0b100000.
Ядро linux имеет ряд таких удобных битовых операций, которые закодированы наиболее эффективным образом для ряда архитектур. Вы можете найти общие версии в включить / asm-generic / bitops / fls.h (и у друзей), но также смотрите включить / asm-x86 / bitops.h для определения, использующего встроенную сборку, если скорость имеет значение, а переносимость - нет.
Ядро Linux полно неопределенного поведения в битопах. Я не рекомендую никому использовать его (или копировать / вставлять подпрограммы), если они не знают, что делают, или не имеют полного набора самопроверки функциональности.
Быстрый способ сделать это - использовать справочную таблицу. Для 32-битного ввода и 8-битной таблицы поиска требуется всего 4 итерации:
int highest_order_bit(int x)
{
static const int msb_lut[256] =
{
0, 0, 1, 1, 2, 2, 2, 2, // 0000_0000 - 0000_0111
3, 3, 3, 3, 3, 3, 3, 3, // 0000_1000 - 0000_1111
4, 4, 4, 4, 4, 4, 4, 4, // 0001_0000 - 0001_0111
4, 4, 4, 4, 4, 4, 4, 4, // 0001_1000 - 0001_1111
5, 5, 5, 5, 5, 5, 5, 5, // 0010_0000 - 0010_0111
5, 5, 5, 5, 5, 5, 5, 5, // 0010_1000 - 0010_1111
5, 5, 5, 5, 5, 5, 5, 5, // 0011_0000 - 0011_0111
5, 5, 5, 5, 5, 5, 5, 5, // 0011_1000 - 0011_1111
6, 6, 6, 6, 6, 6, 6, 6, // 0100_0000 - 0100_0111
6, 6, 6, 6, 6, 6, 6, 6, // 0100_1000 - 0100_1111
6, 6, 6, 6, 6, 6, 6, 6, // 0101_0000 - 0101_0111
6, 6, 6, 6, 6, 6, 6, 6, // 0101_1000 - 0101_1111
6, 6, 6, 6, 6, 6, 6, 6, // 0110_0000 - 0110_0111
6, 6, 6, 6, 6, 6, 6, 6, // 0110_1000 - 0110_1111
6, 6, 6, 6, 6, 6, 6, 6, // 0111_0000 - 0111_0111
6, 6, 6, 6, 6, 6, 6, 6, // 0111_1000 - 0111_1111
7, 7, 7, 7, 7, 7, 7, 7, // 1000_0000 - 1000_0111
7, 7, 7, 7, 7, 7, 7, 7, // 1000_1000 - 1000_1111
7, 7, 7, 7, 7, 7, 7, 7, // 1001_0000 - 1001_0111
7, 7, 7, 7, 7, 7, 7, 7, // 1001_1000 - 1001_1111
7, 7, 7, 7, 7, 7, 7, 7, // 1010_0000 - 1010_0111
7, 7, 7, 7, 7, 7, 7, 7, // 1010_1000 - 1010_1111
7, 7, 7, 7, 7, 7, 7, 7, // 1011_0000 - 1011_0111
7, 7, 7, 7, 7, 7, 7, 7, // 1011_1000 - 1011_1111
7, 7, 7, 7, 7, 7, 7, 7, // 1100_0000 - 1100_0111
7, 7, 7, 7, 7, 7, 7, 7, // 1100_1000 - 1100_1111
7, 7, 7, 7, 7, 7, 7, 7, // 1101_0000 - 1101_0111
7, 7, 7, 7, 7, 7, 7, 7, // 1101_1000 - 1101_1111
7, 7, 7, 7, 7, 7, 7, 7, // 1110_0000 - 1110_0111
7, 7, 7, 7, 7, 7, 7, 7, // 1110_1000 - 1110_1111
7, 7, 7, 7, 7, 7, 7, 7, // 1111_0000 - 1111_0111
7, 7, 7, 7, 7, 7, 7, 7, // 1111_1000 - 1111_1111
};
int byte;
int byte_cnt;
for (byte_cnt = 3; byte_cnt >= 0; byte_cnt--)
{
byte = (x >> (byte_cnt * 8)) & 0xff;
if (byte != 0)
{
return msb_lut[byte] + (byte_cnt * 8);
}
}
return -1;
}
быстрее всего выполнить, но не набрать;)
Ответ Эриксона быстрее ... Он использует меньше памяти, поэтому он более эффективен.
как запутанный код? Попробуй это:
1 << (число) log2 (x)
Мне нравится это решение, но оно слишком запутанное. По-прежнему получает голоса как самое компактное решение.
Если вы хотите переложить эту работу на FPU, конечно. :)
Математически правильно, но это может быть намного медленнее, чем побитовые операции.
Но в наши дни FPU x86 работают достаточно быстро.
Можно найти log2 с помощью побитового увидеть мой ответ
Если вы сначала приведете его к удвоению, экспоненциальная часть числа легко подскажет вам порядок.
В настоящее время я ищу решение той же проблемы, и от этого у меня болит мозг.
Если вам нужна скорость и вы знаете, какое значение будет во время компиляции, используйте boost::static_log2.
Будьте осторожны с числовыми представлениями. Этот рецепт отлично подходит для положительных 32-битных целых чисел. Но std::log2() возвращает nan для отрицательных аргументов, а int(nan) оценивает 0x80000000, который является наибольшим отрицательным int. Это можно наблюдать для g ++ 7.3, но я боюсь, что это не переносимо. Следующая проблема связана с ошибками округления. Когда вы вводите 64-битные числа, все работает нормально до 2 ^ {48} -1. Но для 2 ^ {49} -1 рецепт возвращает 2 ^ {49}.
Это можно легко решить с помощью существующих библиотечных вызовов.
int highestBit(int v){
return fls(v) << 1;
}
На странице руководства Linux содержится более подробная информация об этой функции и ее аналогах для других типов ввода.
Фактически, ffs возвращает бит самого низкого порядка.
@Soren еще лучше, используйте (8 * sizeof (long long) - __builtin_clzll (v))
@Ragnar, если вы идете таким образом, используйте (CHAR_BIT*sizeof(long long) - __builtin_clzll(v), но я не знаю, что эта формулировка достаточно портативна, чтобы в ней нуждаться. <limits.h>
Разве это не наоборот? Должен быть 1UL << fls(v).
В Ubuntu 20.04 нет справочной страницы для этого, и, похоже, ее нет в glibc. Насколько я могу судить, это изобретение FreeBSD.
// Note doesn't cover the case of 0 (0 returns 1)
inline unsigned int hibit( unsigned int x )
{
unsigned int log2Val = 0 ;
while( x>>=1 ) log2Val++; // eg x=63 (111111), log2Val=5
return 1 << log2Val ; // finds 2^5=32
}
fls полностью соответствует аппаратной инструкции для многих архитектур. Я подозреваю, что это, вероятно, самый простой и быстрый способ сделать это.
1<<(fls(input)-1)
Ясно лучший ответ. Интересно, почему все эти веб-сайты, подобные хакерам, которые так сильно сосредоточены на сокращении количества операций для такого рода проблем, даже не упоминают об этом.
Что будет, если бит не установлен? Как остается один сдвиг на отрицательный счет? Я считаю, что это неопределенное поведение.
@jww Это правильно. fls(0) -> 0, оставляя правую часть << отрицательной, что не определено. В свою защиту, это не было заявлено в проблемной области ;-)
@Fabian - Хорошо, плюс один.
Это должен быть принятый ответ. Я рад, что продолжил прокрутку!
@ krs013 - не знаю. Если не ошибаюсь, он недоступен вне POSIX.
fls - это то же самое, что ffs? Я тестирую ответ в своей среде, используя ffs, и результат кажется неправильным для такого случая, как input = 5.
ffs (найти первый значащий) противоположен fls (найти последний значащий) (fls находит самый старший бит, ffs находит младший значащий бит).
_BitScanReverse(), как сообщается, то же самое для Microsoft, поэтому может использоваться вместо fls(), например, с помощью условного #define
Не знаю, почему не могу использовать функцию fls(). Я пробовал использовать #include <strings.h>, как написано в руководстве, но все еще не нашел ... Он устарел? Я не знаю.
Отличное решение, которое я придумал, - это двоичный поиск битов.
uint64_t highestBit(uint64_t a, uint64_t bit_min, uint64_t bit_max, uint16_t bit_shift){
if (a == 0) return 0;
if (bit_min >= bit_max){
if ((a & bit_min) != 0)
return bit_min;
return 0;
}
uint64_t bit_mid = bit_max >> bit_shift;
bit_shift >>= 1;
if ((a >= bit_mid) && (a < (bit_mid << 1)))
return bit_mid;
else if (a > bit_mid)
return highestBit(a, bit_mid, bit_max, bit_shift);
else
return highestBit(a, bit_min, bit_mid, bit_shift);
}
Максимальный бит - это максимальная степень 2, поэтому для 64-битного числа это будет 2 ^ 63. Битовый сдвиг должен быть инициализирован половиной количества бит, поэтому для 64 бит это будет 32.
Если вам не нужно переносимое решение и ваш код выполняется на процессоре, совместимом с x86, вы можете использовать встроенную функцию _BitScanReverse (), предоставляемую компилятором Microsoft Visual C / C++. Он сопоставляется с инструкцией ЦП BSR, которая возвращает самый высокий набор битов.
Немного поздно для этой вечеринки, но самое простое решение, которое я нашел, учитывая современный GCC в качестве компилятора, просто:
static inline int_t get_msb32 (register unsigned int val)
{
return 32 - __builtin_clz(val);
}
static inline int get_msb64 (register unsigned long long val)
{
return 64 - __builtin_clzll(val);
}
Он даже относительно портативен (по крайней мере, он будет работать на любой платформе GCC).
Также поддерживается clang.
Лучший алгоритм, который мне очень нравится:
unsigned hibit(unsigned n) {
n |= (n >> 1u);
n |= (n >> 2u);
n |= (n >> 4u);
n |= (n >> 8u);
n |= (n >> 16u);
return n - (n >> 1);
}
И это легко расширяется для uint64_t вот так:
uint64_t hibit(uint64_t n) {
n |= (n >> 1u);
n |= (n >> 2u);
n |= (n >> 4u);
n |= (n >> 8u);
n |= (n >> 16u);
n |= (n >> 32u);
return n - (n >> 1);
}
или даже __int128
__int128 hibit(__int128 n) {
n |= (n >> 1u);
n |= (n >> 2u);
n |= (n >> 4u);
n |= (n >> 8u);
n |= (n >> 16u);
n |= (n >> 32u);
n |= (n >> 64u);
return n - (n >> 1);
}
Кроме того, кроссплатформенное решение не зависит от использования компилятора.
Возможный дубликат Каков самый быстрый / самый эффективный способ найти самый высокий установленный бит (msb) в целом числе в C?