Подтвердить что ты не робот

Алгоритм генерации битовой маски

Я столкнулся с этой уникальной проблемой создания битовой маски на основе входного параметра. Например,

если param = 2, тогда маска будет 0x3 (11b) если param = 5, то маска будет 0x1F (1 1111b)

Это я реализовал с помощью цикла for в C, что-то вроде

int nMask = 0;
for (int i = 0; i < param; i ++) {

    nMask |= (1 << i);
}

Я хотел бы знать, есть ли лучший алгоритм ~~~

4b9b3361

Ответ 1

Что касается таких битовых масок, следует отметить, что они всегда на единицу меньше, чем степень двух.

Выражение 1 << n - это самый простой способ получить n-ю степень от двух.

Вы не хотите, чтобы Zero предоставлял битовую маску 00000001, вы хотите, чтобы она предоставляла ноль. Поэтому вам нужно вычесть один.

mask = (1 << param) - 1;

Edit:

Если вы хотите специальный случай для параметра> 32:

int sizeInBits = sizeof(mask) * BITS_PER_BYTE; // BITS_PER_BYTE = 8;
mask = (param >= sizeInBits ? -1 : (1 <<  param) - 1);

Этот метод должен работать для 16, 32 или 64-битных целых чисел, но вам, возможно, придется явно ввести "1".

Ответ 2

Эффективная, нерентабельная, портативная и универсальная (но угрюмая) реализация

С

#include <limits.h>     /* CHAR_BIT */

#define BIT_MASK(__TYPE__, __ONE_COUNT__) \
    ((__TYPE__) (-((__ONE_COUNT__) != 0))) \
    & (((__TYPE__) -1) >> ((sizeof(__TYPE__) * CHAR_BIT) - (__ONE_COUNT__)))

С++:

#include <climits>

template <typename R>
static constexpr R bitmask(unsigned int const onecount)
{
//  return (onecount != 0)
//      ? (static_cast<R>(-1) >> ((sizeof(R) * CHAR_BIT) - onecount))
//      : 0;
    return static_cast<R>(-(onecount != 0))
        & (static_cast<R>(-1) >> ((sizeof(R) * CHAR_BIT) - onecount));
}

Использование (создание констант времени компиляции)

BIT_MASK(unsigned int, 4) /* = 0x0000000f */

BIT_MASK(uint64_t, 26) /* = 0x0000000003ffffffULL */

Пример

#include <stdio.h>

int main()
{
    unsigned int param;
    for (param = 0; param <= 32; ++param)
    {
        printf("%u => 0x%08x\n", param, BIT_MASK(unsigned int, param));
    }
    return 0;
}

Выход

0 => 0x00000000
1 => 0x00000001
2 => 0x00000003
3 => 0x00000007
4 => 0x0000000f
5 => 0x0000001f
6 => 0x0000003f
7 => 0x0000007f
8 => 0x000000ff
9 => 0x000001ff
10 => 0x000003ff
11 => 0x000007ff
12 => 0x00000fff
13 => 0x00001fff
14 => 0x00003fff
15 => 0x00007fff
16 => 0x0000ffff
17 => 0x0001ffff
18 => 0x0003ffff
19 => 0x0007ffff
20 => 0x000fffff
21 => 0x001fffff
22 => 0x003fffff
23 => 0x007fffff
24 => 0x00ffffff
25 => 0x01ffffff
26 => 0x03ffffff
27 => 0x07ffffff
28 => 0x0fffffff
29 => 0x1fffffff
30 => 0x3fffffff
31 => 0x7fffffff
32 => 0xffffffff

Объяснение

Прежде всего, как уже обсуждалось в других ответах, вместо << используется >>, чтобы предотвратить проблему, когда количество сдвигов равно числу бит типа хранения значения. (Спасибо Жюльен ответил выше за идею)

Для удобства обсуждения давайте "создадим экземпляр" макроса с unsigned int как __TYPE__ и посмотрим, что произойдет (предположим, что на данный момент 32 бит):

((unsigned int) (-((__ONE_COUNT__) != 0))) \
& (((unsigned int) -1) >> ((sizeof(unsigned int) * CHAR_BIT) - (__ONE_COUNT__)))

Сфокусируйтесь на:

((sizeof(unsigned int) * CHAR_BIT)

первый. sizeof(unsigned int) известен во время компиляции. Он равен 4 согласно нашему предположению. CHAR_BIT представляет количество бит на char, a.k.a. за байт. Это также известно во время компиляции. Он равен 8 для большинства машин на Земле. Поскольку это выражение известно во время компиляции, компилятор, вероятно, будет выполнять умножение во время компиляции и рассматривать его как константу, которая в этом случае равна 32.

Переместить в:

((unsigned int) -1)

Он равен 0xFFFFFFFF. Кастинг -1 любому беззнаковому типу производит значение "all-1s" в этом типе. Эта часть также является постоянной времени компиляции.

До сих пор выражение:

(((unsigned int) -1) >> ((sizeof(unsigned int) * CHAR_BIT) - (__ONE_COUNT__)))

на самом деле то же самое, что:

0xffffffffUL >> (32 - param)

что является таким же, как ответ Жюльена выше. Одна из проблем с его ответом состоит в том, что если param равно 0, вызывая выражение 0xffffffffUL >> 32, результат выражения будет 0xffffffffUL вместо ожидаемого 0! (Вот почему я называю свой параметр как __ONE_COUNT__, чтобы подчеркнуть его намерение)

Чтобы решить эту проблему, мы могли бы просто добавить специальный случай для __ONE_COUNT equals 0 с помощью if-else или ?:, например:

#define BIT_MASK(__TYPE__, __ONE_COUNT__) \
    (((__ONE_COUNT__) != 0) \
    ? (((__TYPE__) -1) >> ((sizeof(__TYPE__) * CHAR_BIT) - (__ONE_COUNT__)))
    : 0)

Но безрежимный код более холодный, не так ли?! Перейдите к следующей части:

((unsigned int) (-((__ONE_COUNT__) != 0)))

Пусть начнется от самого внутреннего выражения до самого внешнего. ((__ONE_COUNT__) != 0) создает 0, когда параметр 0 или 1 в противном случае. (-((__ONE_COUNT__) != 0)) создает 0, когда параметр 0 или -1 в противном случае. Для ((unsigned int) (-((__ONE_COUNT__) != 0))) трюк типа ((unsigned int) -1) уже описан выше. Вы заметили трюк сейчас? Выражение:

((__TYPE__) (-((__ONE_COUNT__) != 0)))

равно "all-0s", если __ONE_COUNT__ равно нулю, а "all-1s" - в противном случае. Он действует как битовая маска для значения, которое мы вычислили на первом этапе. Итак, если __ONE_COUNT__ отличен от нуля, маска как эффект не действует, и это то же самое, что и Жюльен. Если __ONE_COUNT__ 0, он маскирует все биты ответа Жюльена, создавая постоянный нуль. Чтобы визуализировать, смотрите это:

__ONE_COUNT__ :                           0                Other
                                          -------------    --------------
(__ONE_COUNT__)                           0 = 0x000...0    (itself)
((__ONE_COUNT__) != 0)                    0 = 0x000...0     1 = 0x000...1
((__TYPE__) (-((__ONE_COUNT__) != 0)))    0 = 0x000...0    -1 = 0xFFF...F

Ответ 3

В качестве альтернативы вы можете использовать сдвиг вправо, чтобы избежать проблемы, упомянутой в решении (1 << param) - 1.

unsigned long const mask = 0xffffffffUL >> (32 - param);

предполагая, что param <= 32, конечно.

Ответ 4

Для тех, кого это интересует, это альтернатива таблицы поиска, обсуждаемая в комментариях к другому ответу - разница в том, что она корректно работает для параметра 32. Это достаточно просто, чтобы перейти к 64-битной версии unsigned long long, если вам это нужно и не должно быть значительно отличающимся по скорости (если он вызвал в узком внутреннем цикле, тогда статическая таблица останется в кэше по крайней мере L2, и если он не будет вызван в узкий внутренний цикл, тогда разница в производительности выиграет ' t важно).

unsigned long mask2(unsigned param)
{
    static const unsigned long masks[] = {
        0x00000000UL, 0x00000001UL, 0x00000003UL, 0x00000007UL,
        0x0000000fUL, 0x0000001fUL, 0x0000003fUL, 0x0000007fUL,
        0x000000ffUL, 0x000001ffUL, 0x000003ffUL, 0x000007ffUL,
        0x00000fffUL, 0x00001fffUL, 0x00003fffUL, 0x00007fffUL,
        0x0000ffffUL, 0x0001ffffUL, 0x0003ffffUL, 0x0007ffffUL,
        0x000fffffUL, 0x001fffffUL, 0x003fffffUL, 0x007fffffUL,
        0x00ffffffUL, 0x01ffffffUL, 0x03ffffffUL, 0x07ffffffUL,
        0x0fffffffUL, 0x1fffffffUL, 0x3fffffffUL, 0x7fffffffUL,
        0xffffffffUL };

    if (param < (sizeof masks / sizeof masks[0]))
        return masks[param];
    else
        return 0xffffffffUL; /* Or whatever else you want to do in this error case */
}

Стоит отметить, что если вам нужен оператор if() (потому что вы беспокоитесь, что кто-то может назвать его с помощью param > 32), это не приведет к чему-либо другому по сравнению с другим ответом:

unsigned long mask(unsigned param)
{
    if (param < 32)
        return (1UL << param) - 1;
    else
        return -1;
}

Единственное отличие состоит в том, что последняя версия имеет специальный случай param >= 32, тогда как первый имеет только специальный случай param > 32.

Ответ 5

Как насчет этого (на Java):

int mask = -1;
mask = mask << param;
mask = ~mask;

Таким образом, вы можете избежать поисковых таблиц и жесткого кодирования длины целого числа.

Объяснение: целое число со знаком со значением -1 представлено в двоичном виде как все. Shift оставил заданное количество раз, чтобы добавить, что многие 0 вправо. Это приведет к созданию "обратной маски". Затем смените результат смещения, чтобы создать свою маску.

Это может быть сокращено до:

int mask = ~(-1<<param);

Пример:

int param = 5;
int mask = -1;        // 11111111 (shortened for example)
mask = mask << param; // 11100000
mask = ~mask;         // 00011111

Ответ 6

Если вас беспокоит переполнение в C-подобном языке с (1 << param) - 1 (когда param равен 32 или 64 при типе максимального размера, маска становится равной 0, так как битовое смещение выходит за границы типа), одно решение, о котором я только что подумал:

const uint32_t mask = ( 1ul << ( maxBits - 1ul ) ) | ( ( 1ul << ( maxBits - 1ul ) ) - 1ul );

Или другой пример

const uint64_t mask = ( 1ull << ( maxBits - 1ull ) ) | ( ( 1ull << ( maxBits - 1ull ) ) - 1ull );

Вот шаблонизированная версия, имейте в виду, что вы должны использовать ее с беззнаковым типом R:

#include <limits.h>     /* CHAR_BIT */

// bits cannot be 0
template <typename R>
static constexpr R bitmask1( const R bits )
{
    const R one = 1;
    assert( bits >= one );
    assert( bits <= sizeof( R ) * CHAR_BIT );
    const R bitShift = one << ( bits - one );
    return bitShift | ( bitShift - one );
}

Допустим, максимальные биты равны 8 с байтом, с первой функцией переполнения у нас будет 1 << 8 == 256, которая при приведении к байту становится 0. С моей функцией мы имеем 1 << 7 == 128, который может содержать байт, так что становится 1<<7 | 1<<7 - 1.

Я не скомпилировал функцию, поэтому она может содержать опечатки.


И для забавы здесь Жюльен Ройер конкретизировал:

// bits can be 0
template <typename R>
static constexpr R bitmask2( const R bits )
{
    const R zero = 0;
    const R mask = ~zero;
    const R maxBits = sizeof( R ) * CHAR_BIT;
    assert( bits <= maxBits );
    return mask >> ( maxBits - bits );
}

Ответ 7

Просто для справки (Google), я использовал следующее, чтобы получить маску all 1 для целочисленных типов.
В C++ можно просто использовать:

std::numeric_limits<uint_16t>::max() // 65535