Найдите бит наивысшего порядка в C

то, что мне нужно, - это то, во что я могу ввести число, и оно вернет бит наивысшего порядка. Я уверен, что есть простой способ. Ниже приведен пример вывода (слева - ввод)

1 -> 1
2 -> 2
3 -> 2
4 -> 4
5 -> 4
6 -> 4
7 -> 4
8 -> 8
9 -> 8
...
63 -> 32
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
43
1
55 251
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

На ум приходит постоянное удаление младшего бита ...

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)

Paul Hargreaves 10.09.2008 10:21

На самом деле вам нужно, чтобы проверить на ноль. Поскольку ret начинается с 1 и растет, этот алгоритм не заставит его достичь 0, ответ на ввод 0.

Kyle Cronin 10.09.2008 18:39

Мой ответ лучше. Никаких петель!

erickson 20.09.2008 04:01

хороший компилятор развернет циклы для повышения производительности. Если он действительно работает быстрее без цикла.

davenpcj 21.02.2012 21:09

Для GCC лучшей функцией является __builtin_clz (v) - она ​​возвращает количество ведущих (двоичных) нулей, и, следовательно, вы можете получить самую значимую битовую позицию с помощью 32-clz (num) (или 64-clzll (num) для 64 бит)

Soren 17.10.2013 20:56

Из «Восторга хакера»:

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 28.05.2013 19:48

@alveko, хотя XOR может быть реализован быстрее, чем вычитание на уровне транзистора / затвора, они будут занимать одинаковое количество тактовых циклов на большинстве современных процессоров

Drew McGowen 09.01.2015 02:49

меньше транзисторов, больше КПД, меньше тепла, больше циклов :)

Steazy 16.02.2016 11:42

Для интуиции это "расширяет" старший бит вправо, так что 0b100010 становится 0b111111, затем сдвигается вправо на 0b011111 и вычитает это, давая 0b100000.

Warty 16.06.2018 13:04

Ядро linux имеет ряд таких удобных битовых операций, которые закодированы наиболее эффективным образом для ряда архитектур. Вы можете найти общие версии в включить / asm-generic / bitops / fls.h (и у друзей), но также смотрите включить / asm-x86 / bitops.h для определения, использующего встроенную сборку, если скорость имеет значение, а переносимость - нет.

Ядро Linux полно неопределенного поведения в битопах. Я не рекомендую никому использовать его (или копировать / вставлять подпрограммы), если они не знают, что делают, или не имеют полного набора самопроверки функциональности.

jww 21.09.2014 04:46

Быстрый способ сделать это - использовать справочную таблицу. Для 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;
}

быстрее всего выполнить, но не набрать;)

kenny 21.09.2009 19:12

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

Calmarius 20.09.2013 17:39

как запутанный код? Попробуй это:

1 << (число) log2 (x)

Мне нравится это решение, но оно слишком запутанное. По-прежнему получает голоса как самое компактное решение.

Harley Holcombe 11.09.2008 04:09

Если вы хотите переложить эту работу на FPU, конечно. :)

Jeffrey Hantin 23.01.2009 06:52

Математически правильно, но это может быть намного медленнее, чем побитовые операции.

mateusza 02.02.2011 15:36

Но в наши дни FPU x86 работают достаточно быстро.

Prof. Falken 07.09.2012 13:37

Можно найти log2 с помощью побитового увидеть мой ответ

bobobobo 11.09.2012 23:10

Если вы сначала приведете его к удвоению, экспоненциальная часть числа легко подскажет вам порядок.

Calmarius 20.09.2013 17:35

В настоящее время я ищу решение той же проблемы, и от этого у меня болит мозг.

Joshua 22.09.2013 07:41

Если вам нужна скорость и вы знаете, какое значение будет во время компиляции, используйте boost::static_log2.

Cory-G 01.08.2014 03:34

Будьте осторожны с числовыми представлениями. Этот рецепт отлично подходит для положительных 32-битных целых чисел. Но std::log2() возвращает nan для отрицательных аргументов, а int(nan) оценивает 0x80000000, который является наибольшим отрицательным int. Это можно наблюдать для g ++ 7.3, но я боюсь, что это не переносимо. Следующая проблема связана с ошибками округления. Когда вы вводите 64-битные числа, все работает нормально до 2 ^ {48} -1. Но для 2 ^ {49} -1 рецепт возвращает 2 ^ {49}.

hermannk 26.01.2018 01:07

Это можно легко решить с помощью существующих библиотечных вызовов.

int highestBit(int v){
  return fls(v) << 1;
}

На странице руководства Linux содержится более подробная информация об этой функции и ее аналогах для других типов ввода.

Фактически, ffs возвращает бит самого низкого порядка.

Jivlain 19.03.2012 05:56

@Soren еще лучше, используйте (8 * sizeof (long long) - __builtin_clzll (v))

Ragnar 17.09.2015 23:11

@Ragnar, если вы идете таким образом, используйте (CHAR_BIT*sizeof(long long) - __builtin_clzll(v), но я не знаю, что эта формулировка достаточно портативна, чтобы в ней нуждаться. <limits.h>

Jasen 08.12.2017 01:54

Разве это не наоборот? Должен быть 1UL << fls(v).

Peter Cordes 25.08.2019 04:15

В Ubuntu 20.04 нет справочной страницы для этого, и, похоже, ее нет в glibc. Насколько я могу судить, это изобретение FreeBSD.

Nate Eldredge 06.07.2020 07:28

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

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

Cimbali 04.03.2014 23:07

Что будет, если бит не установлен? Как остается один сдвиг на отрицательный счет? Я считаю, что это неопределенное поведение.

jww 21.09.2014 04:50

@jww Это правильно. fls(0) -> 0, оставляя правую часть << отрицательной, что не определено. В свою защиту, это не было заявлено в проблемной области ;-)

Fabian 23.09.2014 04:51

@Fabian - Хорошо, плюс один.

jww 23.09.2014 04:52

Это должен быть принятый ответ. Я рад, что продолжил прокрутку!

krs013 13.02.2015 22:25

@ krs013 - не знаю. Если не ошибаюсь, он недоступен вне POSIX.

AturSams 29.09.2015 18:54

fls - это то же самое, что ffs? Я тестирую ответ в своей среде, используя ffs, и результат кажется неправильным для такого случая, как input = 5.

xxks-kkk 02.01.2017 09:14

ffs (найти первый значащий) противоположен fls (найти последний значащий) (fls находит самый старший бит, ffs находит младший значащий бит).

berkus 03.04.2017 21:29

_BitScanReverse(), как сообщается, то же самое для Microsoft, поэтому может использоваться вместо fls(), например, с помощью условного #define

Jasen 08.12.2017 02:03

Не знаю, почему не могу использовать функцию fls(). Я пробовал использовать #include <strings.h>, как написано в руководстве, но все еще не нашел ... Он устарел? Я не знаю.

lettomobile 20.05.2020 01:55
fls не определен POSIX, хотя ffs определен.. glibc hasn't got it either. FreeBSD does, but OpenBSD doesn't.
Nate Eldredge 06.07.2020 07:26

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

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.

Nate Eldredge 06.07.2020 07:28

Лучший алгоритм, который мне очень нравится:

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

Кроме того, кроссплатформенное решение не зависит от использования компилятора.

Другие вопросы по теме