2的整数次幂?
2的整数次幂的特点
2的整数次幂的数,这里整数我们特指非负整数。
数学表示为$ 2^n, n \in N $,$ N $ 为非负整数(自然数)。我们看看这些数对应的二进制表示:
2的整数次幂 | 幂次 | 二进制 |
---|---|---|
$2 ^ 0 = 1$ | 0 | 00000001 |
$2 ^ 1 = 2$ | 1 | 00000010 |
$2 ^ 2 = 4$ | 2 | 00000100 |
$2 ^ 3 = 8$ | 3 | 00001000 |
$2 ^ 4 = 16$ | 4 | 00010000 |
$2 ^ 5 = 32$ | 5 | 00100000 |
$2 ^ 6 = 64$ | 6 | 01000000 |
$2 ^ 7 = 128$ | 7 | 10000000 |
可以看到,这些数的二进制表示只有一个1,其余位为0。在编程时,2的n次幂的数可以使用数字1左移n位,即1 << n
。
如果将这个数减去1,便从最高的1位往下,全变为1了。用$2^3 = 8$举例:
|
|
判断一个数是否为2的n次幂
根据2的n次幂的二进制特点,我们可以知道,如果这个数x
,与(x - 1)
求逻辑与,结果为0时,则这个数时2的n次幂的数:
|
|
但是,上面的程序遇到n = 0
时,也认为它是2的n次幂的。所以需要改进一下:
|
|
因为n
为0的情况相对较少,所以把and n
条件放在后面。
用c语言写起来可能更清爽:
|
|
较大的2的整数次幂的数
问题描述
求一个数临近的、且较大的2的整数次幂的数,示例:
原数 | 原数的二进制 | 求解数 | 求解数的二进制 |
---|---|---|---|
0 | 00000000 | 1 | 00000001 |
3 | 00000011 | 4 | 00000100 |
4 | 00000100 | 4 | 00000100 |
5 | 00000101 | 8 | 00001000 |
8 | 00001000 | 8 | 00001000 |
9 | 00001001 | 16 | 00010000 |
11 | 00001011 | 16 | 00010000 |
一个简单的求解方法
用python
脚本举例:
|
|
该方法是最容易想到的,利用二进制找最高位的1。原数右移一位,如果不为0,说明还有1,被求解数就左移一次。
代码简单,但求解过程中运算次数不确定。比如输入0,while
循环直接跳过;如果输入256,循环将执行8次。如果函数被大量调用,且输入都是更大的数,必然导致运算量增大。那么可以怎么优化呢?
利用求解数特点来优化
先上代码,长得帅的可能一看就明白了。一段只支持8 bit
表示的数(如果数更大,请手动扩展):
|
|
边界0、会触及溢出的大数需要特殊处理。这个程序更多瞄准非边界的数。
现在无论求什么数,只需要相同几步即可求解。
用数字65来模拟代码运行,为了方便查看,我用-
代替0
:
步骤 | n | n的二进制 |
---|---|---|
n |
65 | -1-----1 |
n = n - 1 |
64 | -1------ |
n |= n >> 1 |
96 | -11----- |
n |= n >> 2 |
120 | -1111--- |
n |= n >> 4 |
127 | -1111111 |
n + 1 |
128 | 1------- |
通过类似模拟,程序的基本思路就是先把数转化位-1111111
的形式,最后+1
得到10000000
,即为所求。
为了正确求解8
、64
等2的n次幂的数,第一行使用n = n - 1
做一下适配。
现在看为什么右移1
、2
、4
呢?从表中可以看出(描述中所谓的最高位,特指从二进制表示时为1的高位开始算;保证n位为1
特指如果从最高1位开始,如果还有n位的空余,那么这些空余位上都是1):
n |= n >> 1
保证数的最高2位都是1n |= n >> 2
最高2位都是1,所以再右移2位,求或之后保证得到最高4位为1n |= n >> 4
最高4位都是1,所以再右移4位,求或之后保证得到最高8位为1- 如果是
32 bit
表示的数,那么追加n |= n >> 8
和n |= n >> 16
。更大的数,以此类推。
这个问题的实际应用
为什么程序员要浪费时间来优化这个方法?求得2的n次幂的数又有什么用?难道仅仅是炫技好玩?又或者为了求职面试?
其实最好用的场景就是取模运算。
人类世界倾向与十进制计数,计算机基于电路开闭的基本设计,倾向于二进制计数。
对于十进制世界的人类来说,求任意一个整数的相对于10的3次幂的模,比如7625 % (10^3)
,只用取低位值625
便可,非常简单。但对于计算机来说,这个计算会稍微麻烦一点。
而让计算机求一个数相对于2的3次幂的模,比如14 % (2^3)
,计算机也可以取最低3位值00001110 & (000001000 - 1) = 00001110 & 00000111 = 00000110 = 6
。但对于人来说,求取又会麻烦一点。
如果我们有一张hash表,需要快速的求key。对于人类来说,我们可以对1000取模,非常方便求取key值。对于计算机来说,对2 ^ 10
取模,反而更容易求key。大家可能最容易接触到的场景,就是java
的hashmap
扩容使用了该方法。
hash取模运算也通常使用素数求模,计算key的速度会慢一点,但是在实际应用中,面对未知数据,或者有潜在规律的数据时,一定程度上减少key冲突的情况,加快查询。这个后面单独写一篇个人见解。
另外,有一些算法也会用到2的n次幂的数。比如一般的FFT
快速傅里叶变换中,在分而治之的思想指导下,需要不断的将数据分成2等分。我也是看FFT
相关代码才关注这个问题的。
扩展:较小的2的整数次幂的数
借用求“较大”的数的思路,求“较小”的数怎么操作呢?
我写了一段代码,同样要注意边界0、触及溢出的大数要特殊处理,这里只展示主体:
|
|
好了,求较小的数有代码了,但我也不知道有什么用,我也不想了,头发快没有了。