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

Как эффективно вычислять 2 ^ n-1 без переполнения?

Я хочу рассчитать 2 n -1 для 64-битного целочисленного значения. Что я сейчас делаю, это

for(i=0; i<n; i++) r|=1<<i;

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

Я думал о

  r=(1ULL<<n)-1;

но он не работает для n=64, потому что << определяется только для значений n до 63.


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

<Предварительно > г = N2MINUSONE_LUT [п]; 3.9 lookup table = fastest, ответ от aviraldg r = n? ~ 0ull > (64 - n): 0ull; 5.9 быстрее без LUT, комментарий от Christoph г = (1ULL < < п) -1; 5.9 Явная, но НЕПРАВИЛЬНАЯ! r = (n == 64)? - 1: (1ULL < lt; n) -1; 7.0 Короткие, четкие и довольно быстрые ответы от Gabe г = ((1ULL < < (п/2)) < < ((п + 1)/2)) - 1; 8.2 Ницца, без спецификации. case, ответить, обратившись г = (1ULL < < п-1) + ((1ULL < < п-1) -1); 9.2 Ницца, без спецификации. case, ответ от David Lively r = pow (2, n) -1; 99.0 Просто для сравнения для (i = 0; я < n; я ++) r | = 1 < i; 123.7 Мое первоначальное решение = ламе

Я принял

r =n?~0ull>>(64 - n):0ull;

как ответ, потому что это, на мой взгляд, самое изящное решение. Это был Christoph, который сначала придумал это, но, к сожалению, он только разместил его в комментарий. Jens Gustedt добавил действительно хорошее объяснение, поэтому я принимаю его ответ вместо этого. Поскольку мне понравилась таблица поиска , она получила 50 очков репутации через награду.

4b9b3361

Ответ 1

Мне нравится aviraldg лучше. Просто, чтобы избавиться от вещей `ULL 'и т.д. На C99 я бы сделал

static inline uint64_t n2minusone(unsigned n) {
   return n ? (~(uint64_t)0) >> (64u - n) : 0;
}

Чтобы убедиться, что это действительно

  •  
  • uint64_t гарантированно имеет ширину ровно 64 бит.  
  • бит-отрицание этого "нуля типа uint64_t" имеет, таким образом,  64 одного бита  
  • смещение вправо без знака гарантируется как логическое  сдвиг, поэтому все заполнено нулями слева.  
  • сдвиг со значением, равным или большим ширине, равен undefined, поэтому  да, вы должны сделать хотя бы одно условие, чтобы быть уверенным в вашем результате.  
  • встроенная функция (или, альтернативно, приведение к uint64_t, если вы  предпочитают) делает этот тип безопасным; a unsigned long long может  а в будущем будет иметь значение 128 бит.  Функция
  • a static inline должна быть плавно  встроенный в вызывающего абонента без каких-либо накладных расходов

Ответ 2

Используйте таблицу поиска. (Создано вашим текущим кодом.) Это идеально, поскольку количество значений невелико, и вы уже знаете результаты.

/* lookup table: n -> 2^n-1 -- do not touch */
const static uint64_t N2MINUSONE_LUT[] = {
0x0,
0x1,
0x3,
0x7,
0xf,
0x1f,
0x3f,
0x7f,
0xff,
0x1ff,
0x3ff,
0x7ff,
0xfff,
0x1fff,
0x3fff,
0x7fff,
0xffff,
0x1ffff,
0x3ffff,
0x7ffff,
0xfffff,
0x1fffff,
0x3fffff,
0x7fffff,
0xffffff,
0x1ffffff,
0x3ffffff,
0x7ffffff,
0xfffffff,
0x1fffffff,
0x3fffffff,
0x7fffffff,
0xffffffff,
0x1ffffffff,
0x3ffffffff,
0x7ffffffff,
0xfffffffff,
0x1fffffffff,
0x3fffffffff,
0x7fffffffff,
0xffffffffff,
0x1ffffffffff,
0x3ffffffffff,
0x7ffffffffff,
0xfffffffffff,
0x1fffffffffff,
0x3fffffffffff,
0x7fffffffffff,
0xffffffffffff,
0x1ffffffffffff,
0x3ffffffffffff,
0x7ffffffffffff,
0xfffffffffffff,
0x1fffffffffffff,
0x3fffffffffffff,
0x7fffffffffffff,
0xffffffffffffff,
0x1ffffffffffffff,
0x3ffffffffffffff,
0x7ffffffffffffff,
0xfffffffffffffff,
0x1fffffffffffffff,
0x3fffffffffffffff,
0x7fffffffffffffff,
0xffffffffffffffff,
};

Ответ 3

Как насчет простой r = (n == 64) ? -1 : (1ULL<<n)-1;?

Ответ 4

Если вы хотите получить максимальное значение перед переполнением с заданным количеством бит, попробуйте

r=(1ULL << n-1)+((1ULL<<n-1)-1);

Разделив сдвиг на две части (в этом случае два 63-битных сдвига, так как 2 ^ 64 = 2 * 2 ^ 63), вычитая 1, а затем добавив два результата вместе, вы должны иметь возможность сделать расчет без переполнения 64-битного типа данных.

Ответ 5

if (n > 64 || n < 0)
  return undefined...

if (n == 64)
  return 0xFFFFFFFFFFFFFFFFULL;

return (1ULL << n) - 1;

Ответ 6

Единственная проблема заключается в том, что ваше выражение не определено для n = 64? Тогда специальный случай, что одно значение.

(n == 64 ? 0ULL : (1ULL << n)) - 1ULL

Ответ 7

Смещение 1 < 64 в 64-битном целочисленном значении дает 0, поэтому нет необходимости вычислять что-либо для n > 63; сдвиг должен быть достаточно быстрым

r = n < 64 ? (1ULL << n) - 1 : 0;

Но если вы пытаетесь таким образом узнать максимальное значение, которое может иметь N бит без знака, вы меняете 0 на известное значение, рассматривая n == 64 как частный случай (и вы не можете дать результат для n > 64 на аппаратном обеспечении с 64-битным целым числом, если вы не используете библиотеку multiprecision/bignumber).

Другой подход с битовыми трюками

~-(1ULL << (n-1) ) | (1ULL << (n-1))

проверить, может ли он быть скомплектован... конечно, n > 0

ИЗМЕНИТЬ

Тесты, которые я сделал

__attribute__((regparm(0))) unsigned int calcn(int n)
{
  register unsigned int res;
  asm(
    "  cmpl $32, %%eax\n"
    "  jg   mmno\n"
    "  movl $1, %%ebx\n"      // ebx = 1
    "  subl $1, %%eax\n"      // eax = n - 1
    "  movb %%al, %%cl\n"     // because of only possible shll reg mode
    "  shll %%cl, %%ebx\n"    // ebx = ebx << eax
    "  movl %%ebx, %%eax\n"   // eax = ebx
    "  negl %%ebx\n"          // -ebx
    "  notl %%ebx\n"          // ~-ebx
    "  orl  %%ebx, %%eax\n"   // ~-ebx | ebx
    "  jmp  mmyes\n"
    "mmno:\n"
    "  xor %%eax, %%eax\n"
    "mmyes:\n"
    :
    "=eax" (res):
    "eax" (n):
    "ebx", "ecx", "cc"
    );
  return res;
}

#define BMASK(X) (~-(1ULL << ((X)-1) ) | (1ULL << ((X)-1)))
int main()
{
  int n = 32; //...
  printf("%08X\n", BMASK(n));
  printf("%08X %d %08X\n", calcn(n), n&31, BMASK(n&31));
  return 0;
}

Выход с n = 32 равен -1 и -1, а n = 52 дает "-1" и 0xFFFFF, случайно 52 и 31 = 20, и, конечно, n = 20 дает 0xFFFFF...

EDIT2 теперь код asm создает 0 для n > 32 (так как я на 32-битной машине), но на данный момент решение a ? b : 0 с BMASK более ясное, и я сомневаюсь, что Решение asm слишком быстро (если скорость настолько большая, что идея таблицы может быть быстрее).

Ответ 8

Поскольку вы попросили об этом элегантный способ:

const uint64_t MAX_UINT64 = 0xffffffffffffffffULL;
#define N2MINUSONE(n) ((MAX_UINT64>>(64-(n))))

Ответ 9

Я ненавижу, что (a) n << 64 есть undefined и (b) на популярном аппаратном оборудовании Intel по размеру слова нет-op.

У вас есть три способа пойти сюда:

  • Таблица поиска. Я рекомендую против этого из-за трафика памяти, плюс вы напишете много кода для поддержания трафика памяти.

  • Условная ветвь. Проверьте, соответствует ли n размеру слова (8 * sizeof(unsigned long long)), возвратите ~(unsigned long long)0, в противном случае сдвиньте и вычтите, как обычно.

  • Попытайтесь стать умными с арифметикой. Например, в действительных числах 2^n = 2^(n-1) + 2^(n-1), и вы можете использовать это удостоверение, чтобы убедиться, что вы никогда не используете силу, равную размеру слова. Но вам лучше быть уверенным, что n никогда не равен нулю, потому что, если это так, это тождество не может быть выражено целыми числами, а смещение влево на -1 может укусить вас в задницу.

Я лично пошёл бы с условной ветвью, это труднее всего испортить, явно обрабатывает все разумные случаи n, а с современным оборудованием вероятность ошибочного предсказания ветки невелико. Вот что я делаю в своем реальном коде:

/* What makes things hellish is that C does not define the effects of
   a 64-bit shift on a 64-bit value, and the Intel hardware computes
   shifts mod 64, so that a 64-bit shift has the same effect as a
   0-bit shift.  The obvious workaround is to define new shift functions 
   that can shift by 64 bits. */

static inline uint64_t shl(uint64_t word, unsigned bits) {
  assert(bits <= 64);
  if (bits == 64)
    return 0;
  else
    return word << bits;
}

Ответ 10

Я думаю, что проблема, которую вы видите, вызвана тем, что (1<<n)-1 оценивается как (1<<(n%64))-1 на некоторых фишках. Особенно, если n оптимизируется или может быть оптимизирован как константа.

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

((1ULL<<(n/2))<<((n+1)/2))-1;

Вам нужно будет измерить, будет ли это быстрее, чем специальный корпус 64:

(n<64)?(1ULL<<n)-1:~0ULL;

Ответ 11

Верно, что в C каждая операция смещения бит должна сдвигаться на меньшее число бит, чем в операнде (в противном случае поведение undefined). Однако никто не запрещает вам выполнять смену в два последовательных шага.

r = ((1ULL << (n - 1)) << 1) - 1;

т.е. сначала смените бит n - 1, а затем сделайте дополнительный 1-битный сдвиг. В этом случае, конечно, вам придется обрабатывать ситуацию n == 0 особым образом, если это допустимый ввод в вашем случае.

В любом случае, это лучше, чем ваш цикл for. Последняя, ​​по сути, является одной и той же идеей, но по какой-то причине доведена до предела.

Ответ 12

Ub = universe in bits = lg(U):
high(v) = v >> (Ub / 2)
low(v) = v & ((~0) >> (Ub - Ub / 2))  // Deal with overflow and with Ub even or odd