而且有循环右移的要求,所以需要用无符号整数来表示(因为对于有符号整数,最高位为符号位,右移时,会补充符号位)
我们以8位二进制整数为例,讲解一些运算
最高位在左,最低位在右(由于存在大端机和小端机的区别,导致其物理存储顺序不同,但仅考虑位运算时,你并不需要区分)
比如 5 == 2^2 + 2^05==2
2
+2
0
可以表示为
00000101
其右移1位即(移出的位会被省略)
00000010
其左移1位即
00001010
其左移6位即 (移出的位会被省略)
10000000
那么如何表示负数呢,在C以及绝大部分计算机应用中,使用补码方式,
简单理解就是:比如x是一个非负整数,那么-x就是将x的二进制取反,然后+1
比如1的二进制为00000001
按位取反(即每一位翻转)
11111110
+1
11111111
比如2
00000010
按位取反
11111101
+1
11111110
比如0
00000000
按位取反
11111111
+1
00000000 (进位溢出会被省略)
特别得如果这是一个有符号整数,且最高位(即符号位)为1
比如-2
11111110
其右移1位并不是
01111111
而是
11111111
因为其前方因为右移移出的空位(在左侧)会被替换为符号位(在这里为1)
比如-128其为
10000000
右移2位为
11100000
整数的右移,是指将所有位向右可以看作除以2下取整比如, 1 >> 1 == 0, 0 >> 1 == 0, 2 >> 1 == 1, -3 >> 1 == -2, -1 >> 1 == -1
循环右移指的是将向右溢出的字符补充到左侧空出来得位置,比如
00011101 循环右移 1位
结果为
10001110
00011101 循环右移 3位
10100011
我们发现当x_{[7,0]}x
[7,0]
循环右移y(1 <= y <= 7)位时,其变成了x_{[y-1, 0], [7 , y]}x
[y−1,0],[7,y]
若y >= 8 或y < 0 可以令y对8取模,(y = (y % 8 + 8) % 8; )
如果y == 0相当于不变(当然,你不一定要特判y == 0,好的实现方法可以兼容这种情况,如果不够好,可以特判一下)
理论上你知道这个性质,再加上或运算就可以得到循环位移的写法了
如果想不出,可以百度“c++循环右移”
与,或,异或,按位取反
与:a & b
对于每一位若两者均为1,则为1,否则为0
比如
11001101 & 00110101
==
00000101
或:a | b
对于每一位若两者均为0,则为0,否则为1
11001101 | 00110101
==
11111101
异或:a ^ b
对于每一位若两者相同,则为0,否则为1
11001101 ^ 00110101
==
11111000
按位取反: ~a
~11001101
==
00110010
当(a & b) == 0时
(a ^ b) == (a | b)
这里加括号是为了运算符优先级
注意区分& 和 &&; | 和 ||
我们如何取出低i位呢
我们令mask(i) = 表示低i位均为1,其余均为0
比如(这里的=是数学上的等于)mask(5) = 00011111 = 2^5 - 12
5
−1 = (1 << 5) - 1
特别的,对于8位无符号整数mask(8) = 11111111 = ~0 = -1 \not=
= (1 << 8) - 1
但是在实际运算时,不足32位的整数在运算时会自动转化为32位的,所以中在C可以用(1 << 8) - 1
但对于 >= 32位的整数,比如128位整数, 不能用 ((__uint128_t)1 << 128) - 1来表示全1, 可以用((__uint128_t)-1) 或者 (~(__uint128_t)0)表示
不考虑自动类型转化的话,继续以8位无符号整数举例
1 << (7 + 1) 和 1 << 7 << 1 是不等价的
但1 << (8 + 1) 和 1 << 8 << 1 是等价的
因为位移时,比如a << b; 只考虑b的低三位,即b mod 8的值
故1 << 8 == 1 << 0
在b为负数时同理,a >> -2 即为 a >> (8 - 2)
a << -2 即为 a << (8 - 2)
回到mask(i)
a mod 2^i2
i
= a & mask[i] ;//a为负数时也成立,这里的mod是数学意义上的mod,已经取正
mask(l) ^ mask(r + 1)即第l位到第r位均为1,其余为0
也可以用 mask(r - l + 1) << l 表示
这里的思想称为前缀和(当然,不一定是前缀,可以是后缀,和也不一定是数的相加)
有的同学发现了一个单步循环移位的方法
for (int i = 1; i <= x; ++i) a = (a >> 1) | ((a & 1) << 127)
根据位移的运算规则a & 1可省略
改为
for (int i = 1; i <= x; ++i) a = (a >> 1) | (a << 127)
那么怎么把移动1步,改为移动2步乃至x步呢?
正数反码、补码等于原码