如何有效地计算2 ^ n-1而不溢出?

时间:2010-06-13 16:19:52

标签: c bit-manipulation

我想为64位整数值计算2 n -1。 我目前做的是这个

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

我想知道是否有更优雅的方式来做到这一点。 这条线在内环中,所以我需要它很快。

我想到了

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

但它不适用于n=64,因为<<仅定义 对于n最多63的值。


修改 感谢您的所有答案和评论。 这是一个小桌子,上面有我最好尝试和喜欢的解决方案。 第二列是我(完全不科学的)基准时间的秒数。

    
r=N2MINUSONE_LUT[n];            3.9 lookup table = fastest, answer by aviraldg
r =n?~0ull>>(64 - n):0ull;      5.9 fastest without LUT, comment by Christoph
r=(1ULL<<n)-1;                  5.9 Obvious but WRONG!   
r =(n==64)?-1:(1ULL<<n)-1;      7.0 Short, clear and quite fast, answer by Gabe
r=((1ULL<<(n/2))<<((n+1)/2))-1; 8.2 Nice, w/o spec. case, answer by drawnonward
r=(1ULL<<n-1)+((1ULL<<n-1)-1);  9.2 Nice, w/o spec. case, answer by David Lively
r=pow(2, n)-1;               99.0 Just for comparison
for(i=0; i<n; i++) r|=1<<i;   123.7 My original solution = lame

我接受了

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

作为答案,因为在我看来这是最优雅的解决方案。 最初提出它的是Christoph,但不幸的是他只是在一个版本中发布了它 commentJens Gustedt增加了一个非常好的理由,所以我接受了他的回答。因为我喜欢Aviral Dasgupta's查询表solution,所以它通过赏金获得了50个声望点。

13 个答案:

答案 0 :(得分:31)

Use a lookup table.(由您现有的代码生成。)这是理想的,因为值的数量很少,而且您已经知道了结果。

/* 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,
};

答案 1 :(得分:25)

一个简单的r = (n == 64) ? -1 : (1ULL<<n)-1;怎么样?

答案 2 :(得分:12)

如果要在溢出之前获取给定位数的最大值,请尝试

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

通过将移位分成两部分(在这种情况下,两个63位移位,因为2 ^ 64 = 2 * 2 ^ 63),减去1然后将两个结果加在一起,您应该能够进行计算没有溢出64位数据类型。

答案 3 :(得分:8)

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

if (n == 64)
  return 0xFFFFFFFFFFFFFFFFULL;

return (1ULL << n) - 1;

答案 4 :(得分:5)

我最喜欢aviraldg回答。 只是为了摆脱C99中的'ULL'东西等我会做

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

要确定这是有效的

     
  • 保证uint64_t的宽度恰好为64位
  •  
  • 因此uint64_t类型为零的位否定就是这样  64位一位
  •  
  • 保证无符号值的右移是合乎逻辑的  转移,所以一切都用左边的零填充
  •  
  • 等于或大于宽度的值的移位是未定义的,所以  是的,你必须做至少一个条件以确保你的结果
  •  
  • 一个内联函数(或者如果你是另一个转换为uint64_t)  喜欢)使这种类型安全; unsigned long long可以  很有可能是未来128位的值
  •  
  • static inline函数应该是无缝的  在没有任何开销的情况下内联在调用者中

答案 5 :(得分:4)

唯一的问题是你的表达式没有为n = 64定义?然后是一个值的特殊情况。

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

答案 6 :(得分:4)

移位1&lt;&lt; 64位整数中的64得到0,因此不需要为n&gt;计算任何东西。 63;转移应该足够快

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

但是如果你试图通过这种方式知道N位无符号整数可以有的最大值,你可以将0改为已知值,将n == 64视为一种特殊情况(并且你无法给出结果除非您使用多精度/ bignumber库,否则在64位整数的硬件上n> 64;

使用位技巧的另一种方法

~-(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&amp; 31 = 20,当然n = 20给出0xFFFFF ...

EDIT2 现在asm代码为n&gt;生成0 32(因为我使用的是32位机器),但此时使用BMASK的a ? b : 0解决方案更加清晰,我怀疑asm解决方案的速度要快得多(如果速度是一个如此大的问题,表格的想法可能会如此是更快的。

答案 7 :(得分:4)

既然你已经要求一种优雅的方式:

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

答案 8 :(得分:3)

我讨厌(a)n << 64未定义且(b)在流行的英特尔硬件上按字大小移位是无操作。

你有三种方法可以到这里:

  1. 查找表。由于内存流量的原因,我建议不要这样做,另外你会写很多代码来维持内存流量。

  2. 条件分支。检查n是否等于字大小(8 * sizeof(unsigned long long)),如果是,请返回~(unsigned long long)0,否则按常规方式移位和减去。

  3. 尝试通过算术聪明一点。例如,在实数2^n = 2^(n-1) + 2^(n-1)中,您可以利用此标识来确保您永远不会使用等于字大小的幂。但是你最好确定n永远不会为零,因为如果它是,那么这个身份不能用整数表示,向左移动-1可能会让你陷入屁股。

  4. 我个人会选择条件分支 - 它是最难搞定的,显然处理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;
    }
    

答案 9 :(得分:2)

我认为您看到的问题是由于(1<<n)-1在某些筹码上评估为(1<<(n%64))-1而导致的。特别是如果n是或可以优化为常数。

鉴于此,您可以做很多微小的变化。例如:

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

您必须进行测量以确定它是否比特殊套管64更快:

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

答案 10 :(得分:2)

确实,在C中,每个位移操作必须移位比操作数中的位少的位(否则,行为是未定义的)。但是,没有人禁止你在两个连续的步骤中进行转变

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

即。首先移位n - 1位然后再进行1位移位。当然,在这种情况下,您必须以特殊方式处理n == 0情况,如果这是您案例中的有效输入。

无论如何,它优于您的for周期。后者基本上是相同的想法,但由于某种原因而走向极端。

答案 11 :(得分:0)

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

答案 12 :(得分:0)

您可以利用整数除法误差并使用指数的模来确保始终在[0,(sizeof(uintmax_t) * CHAR_BIT) - 1]范围内移动,以为支持的最大本机整数创建通用pow2i函数字长,但是,可以很容易地对其进行调整以支持任意字长。

老实说,我不明白为什么这不仅仅是硬件上实现移位移位的实现。

#include <limits.h>
static inline uintmax_t pow2i(uintmax_t exponent) {
    #define WORD_BITS  ( sizeof(uintmax_t) * CHAR_BIT )
    return ((uintmax_t) 1) << (exponent / WORD_BITS) << (exponent % WORD_BITS);
    #undef WORD_BITS
}

从那里,您可以计算pow2i(n) - 1