C 如何在没有溢出的情况下高效地计算2^n-1?

C 如何在没有溢出的情况下高效地计算2^n-1?,c,bit-manipulation,C,Bit Manipulation,我想计算64位整数值的2n-1。 我现在做的是 for(i=0; i<n; i++) r|=1<<i; 我接受了 r =n?~0ull>>(64 - n):0ull; 作为答案,因为在我看来这是最优雅的解决方案。 最初是谁提出的,但不幸的是,他只是在一个网站上发布 . 添加了一个非常好的理由,所以我接受他的回答。因为我喜欢lookup table,它通过赏金获得了50个声誉点。(由您当前的代码生成)。这是理想的,因为值的数量很小,并且您已经知道结果 /* lo

我想计算64位整数值的2n-1。 我现在做的是

for(i=0; i<n; i++) r|=1<<i;
我接受了

r =n?~0ull>>(64 - n):0ull;
作为答案,因为在我看来这是最优雅的解决方案。 最初是谁提出的,但不幸的是,他只是在一个网站上发布 . 添加了一个非常好的理由,所以我接受他的回答。因为我喜欢lookup table,它通过赏金获得了50个声誉点。

(由您当前的代码生成)。这是理想的,因为值的数量很小,并且您已经知道结果

/* 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,
};
if(n>64 | | n<0)
返回未定义的。。。
如果(n==64)
返回0xFFFFFFFFFFFFFFFULL;

return(1ULL一个简单的
r=(n==64)怎么样?-1:(1ULL唯一的问题是没有为n=64定义表达式?然后是一个值的特殊情况

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

(n==64?0ULL:(1ULL如果要在给定位数溢出之前获取最大值,请尝试

r=(1ULL << n-1)+((1ULL<<n-1)-1);
r=(ll1换档1 63;换档应该足够快

r = n < 64 ? (1ULL << n) - 1 : 0;
r=n<64?(除非使用多精度/bignumber库,否则在64位整数的硬件上使用64位)

另一种使用比特技巧的方法

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

~-(1all因为您要求一种优雅的方式:

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

我认为你看到的问题是因为我讨厌我最喜欢的答案。 为了去掉C99中的“ULL”等东西,我会这样做

static inline uint64_t n2minusone(unsigned n) { return n ? (~(uint64_t)0) >> (64u - n) : 0; } 静态内联uint64\u t n2musone(无符号n){ 返回n(~(uint64_t)0)>>(64u-n):0; } 确保这是有效的

  • uint64_t的宽度保证正好为64位
  • 因此,“uint64_t类型的零”的位求反 64一位
  • 无符号值的右移保证为逻辑值 移位,所以所有的东西都由左边的零填充
  • 值等于或大于宽度的shift未定义,因此 是的,您必须至少执行一个条件才能确保结果
  • 一个内联函数(或者,如果您 首选)使此类型安全;
    unsigned long
    将来将是128位宽的值
  • 静态内联
    函数应该是无缝的 在调用方中内联,没有任何开销

的确,在C中,每个位移位操作的移位量必须小于操作数中的位数(否则,行为是未定义的)。但是,没有人禁止您在两个连续步骤中进行移位

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

r=((1all您可以利用整数除法的不精确性,并使用指数的模来确保始终在[0,
(sizeof(uintmax)×CHAR\u 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
}
#包括
静态内联uintmax\u t功率2i(uintmax\u t指数){
#定义字位(sizeof(uintmax\u t)*字符位)


return((uintmax_t)1)这实际上非常有用,因为这些值是提前知道的。如果值是在Hex中定义的,则可能看起来不那么神秘,并使表保持静态常量,因为它不会改变。:)@保罗:不是。我也没有这样说。OP要求速度和优雅。这些都不具备可读性。我可以建议作为一个注释
/*查找表:n->2^n-1--不要触摸*/
。是的,因为1只是为了比较:我的for-loop解决方案对我的特定问题花了120秒。@ShinTakezou:如果速度是你的pr这是不是一个问题,坚持到硬件依赖的优化,我会写在ASM,在那里你顺便也可以“访问”更多的位OPS,这可能是有用的(在这种情况下可能不是无论如何)NAA扔掉C然后,做它很少的ASM线,认为它像每个处理器优化(如果需要移植,您必须重写其他处理器的代码)我喜欢这样,它简洁、易懂且速度相当快(针对我的具体问题,为7.0秒)。
n?~0ull>(64-n):0ull
相当,而且可能更快,因为只有一个64位,相反,我更喜欢~0而不是-1,因为-1意味着(对我来说,不是编译器)你在用有符号整数。@Christoph你是个黑客。原版是5.9秒对7.0秒。David,你赢得了最优雅的解决方案的特价。它比“特殊情况n=64”解决方案(我的特定问题是9.2秒)稍微慢一点@Ludwig,你也许可以通过计算1ULL来加速它。谢谢你的解决方案Shin。问题是1a
nAs答应我看一下你的代码。我不明白你为什么这么做-(1ULL不,除了~-a,什么都不缺,a是获得一个掩码,直到由a中仅有的1位的位置“标记”的位(用64获得的结果不会给出你想要的0),因为你不能做2^64-1,但是~-(2^63)|(2^63)是可以的。基本上你仍然需要?:为n>64返回0,而我前面的注释应该更正为
(n0)?calcn(n):0
。抱歉,目前没有更好的想法。目前表格可能是更快的方法,但仍需要检查表格中元素数的n>。P.S.是的,它作为表达式过于复杂,似乎更简单
n>=64?0:(1在我见过的所有方法中,我最喜欢这个。确定性。无分支。占地面积小。可移植。原始思维。值得称赞!不幸的是,它给出了一个错误的n=0结果。您的查找表有65个条目(不是64个)这是正确的,因为我要求从2^0-1到2^64-1的结果。右操作数移位只有64个可能的有意义的值。这就是为什么我(现在)我认为一次换班是不可能的。@ludwig:看看上面的评论,我注意到克里斯托普的解决方案是完全相同的,除了他是一个特殊的0型外壳。你呢
const uint64_t MAX_UINT64 = 0xffffffffffffffffULL;
#define N2MINUSONE(n) ((MAX_UINT64>>(64-(n))))
((1ULL<<(n/2))<<((n+1)/2))-1;
(n<64)?(1ULL<<n)-1:~0ULL;
/* 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;
}
static inline uint64_t n2minusone(unsigned n) { return n ? (~(uint64_t)0) >> (64u - n) : 0; }
r = ((1ULL << (n - 1)) << 1) - 1;
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
#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
}