我想为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,但不幸的是他只是在一个版本中发布了它 comment。 Jens Gustedt增加了一个非常好的理由,所以我接受了他的回答。因为我喜欢Aviral Dasgupta's查询表solution,所以它通过赏金获得了50个声望点。
答案 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; }
要确定这是有效的
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)在流行的英特尔硬件上按字大小移位是无操作。
你有三种方法可以到这里:
查找表。由于内存流量的原因,我建议不要这样做,另外你会写很多代码来维持内存流量。
条件分支。检查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;
}
答案 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
。