c进阶篇(一)——移位操作详解
c进阶篇(一)——移位操作详解
前言:
嵌入式编程中,很多时候都要进行比特级的操作,移位运算可以简化一些乘除操作。这篇文章带你真正了解移位操作。
1 数据的存储
对数据进行移位操作,首先要了解数据在计算机中是如何存储的。计算机关于数据涉及到原码、反码、补码三种。
1.1 原码
原码是对数字的二进制表示方法。有符号数的原码最高位是符号位,其余为数值域;无符号数则只有数值域,没有符号位。符号位用0表示非负数(0和正数),1表示复数。
由于无符号数没有符号位,因此数据域比有符号数多一位,不能表示负数但能表示更多的非负数。
表示的数值范围是偶数(2^N),比如int8_t能表示256个数,int16_t能表示65536个数等。由于实际运用中不需要-0,便人为规定将-0当做值最小的负数处理,因此int8_t表示的数值范围为-128127,int16_t表示的数值范围为-3276832767。
int8_t | 原码 |
---|---|
0 | 0000 0000b |
5 | 0000 0101b |
-5 | 1000 0101b |
127 | 0111 1111b |
-127 | 1111 1111b |
-0 | 1000 0000b |
-128 | - |
uint8_t | 原码 |
---|---|
0 | 0000 0000b |
5 | 0000 0101b |
255 | 1111 1111 |
1.2 反码
反码就是符号位不变,数据域的二进制按位取反。
int8_t | 原码 | 反码 |
---|---|---|
0 | 0000 0000b | 1111 1111b |
5 | 0000 0101b | 1111 1010b |
-5 | 1000 0101b | 1111 1010b |
127 | 0111 1111b | 1000 0000b |
-127 | 1111 1111b | 1000 0000b |
-0 | 1000 0000b | 1111 1111b |
-128 | - | - |
uint8_t | 原码 | 反码 |
---|---|---|
0 | 0000 0000b | 1111 1111b |
5 | 0000 0101b | 1111 1010b |
255 | 1111 1111 | 0000 0000b |
1.3 补码
计算机中数据都是以补码形式存储的,这样做的目的是可以将符号位和数值域统一处理;同时,加法减法可以统一处理。
非负数补码为其原码,负数补码为其反码 + 1 。如果已知一个负数的补码求原码,可以先将补码 - 1然后求反码,也可再次求反码 + 1即为原码。
int8_t | 原码 | 反码 | 补码 |
---|---|---|---|
0 | 0000 0000b | 1111 1111b | 0000 0000b |
5 | 0000 0101b | 1111 1010b | 0000 0101b |
-5 | 1000 0101b | 1111 1010b | 1111 1011b |
127 | 0111 1111b | 1000 0000b | 0111 1111b |
-127 | 1111 1111b | 1000 0000b | 1000 0001b |
-0 | 1000 0000b | 1111 1111b | - |
-128 | - | - | 1000 0000b |
uint8_t | 原码 | 反码 | 补码 |
---|---|---|---|
0 | 0000 0000b | 1111 1111b | |
5 | 0000 0101b | 1111 1010b | |
255 | 1111 1111 | 0000 0000b |
2 移位的原理
由于数据在计算机中存储的是补码,因此移位操作移动的也是补码,而不是原码。我们知道非负数和负数的补码规则是不同的,因此非负数和负数的移位操作也存在差异。另外移位也有左移和右移的区别。移位移动的是整数,而浮点数在计算机中存储的格式较为复杂,因此目前c不支持浮点数移位操作。
有符号数含有符号位,符号位为0表示非负数1表示负数,符号位的数是参与移位的,右移操作时非负数在高位补0,负数在高位补1,因此负数右移后还是负数,非负数右移后还是非负数。但左移有符号数时非负数可能变负数,负数也可能变非负数,这取决于移动的值。
通常我们认为的右移动是除2^N,左移是乘2^N,只适用于无符号整形,并且在值不溢出的情况下才适用。
以下演示1Byte数据的移位,更多字节数据的移位原理同理。
2.1 非负数移位
计算机中存储补码,由于非负数的补码等于原码,因此对非负数移位操作实际上移动的值是原码。有符号类型非负数符号位是0。
2.1.1 右移
非负数右移,高位补0,低位移出舍弃,由于高位总是补0而符号位0表示非负数,因此有符号类型非负数右移也总是非负数;对于无符号类型,只有数据域无符号位,高位补0,低位移出舍弃。
int8_t | 补码 | >> 1 | >> 2 |
---|---|---|---|
0 | 0000 0000b | 0000 0000b | 0000 0000b |
85 | 0101 0101b | 0010 1010b(42d) | 0001 0101b(21d) |
127 | 0111 1111b | 0011 1111b(63d) | 0001 1111b(31) |
uint8_t | 补码 | >> 1 | >> 2 |
---|---|---|---|
0 | 0000 0000b | 0000 0000b | 0000 0000b |
170 | 1010 1010b | 0101 0101b(85d) | 0010 1010b(42d) |
255 | 1111 1111b | 0111 1111b(127d) | 0011 1111b(63d) |
2.1.2 左移
非负数左移,低位补0,高位移出舍弃,由于低位总是补0而符号位也参与移位,因此有符号类型非负数左移可能变为负数;对于无符号类型,只有数据域无符号位,低位补0,高位移出舍弃。
int8_t | 补码 | << 1 | << 2 |
---|---|---|---|
0 | 0000 0000b | 0000 0000b | 0000 0000b |
85 | 0101 0101b | 1010 1010b(-86d) | 0101 0100b(84d) |
127 | 0111 1111b | 1111 1110b(-2d) | 1111 1100b(-4d) |
uint8_t | 补码 | << 1 | << 2 |
---|---|---|---|
0 | 0000 0000b | 0000 0000b | 0000 0000b |
170 | 1010 1010b | 0101 0100b(84d) | 1010 1000b(168d) |
255 | 1111 1111b | 1111 1110b(254d) | 1111 1100b(252d) |
2.2 负数移位
计算机中存储补码,由于负数的补码不是原码,负数补码为符号位不变反码 + 1。有符号类型负数符号位是1。
2.2.1 右移
负数右移,高位补1,低位移出舍弃,由于高位总是补1而符号位1表示负数,因此有符号类型负数右移也总是负数;对于无符号类型,只有数据域无符号位,高位补1,低位移出舍弃。
int8_t | 补码 | >> 1 | >> 2 |
---|---|---|---|
-1 | 1111 1111b | l111 1111b(-1d) | l111 1111b(-1d) |
-42 | 1101 0110b | 1110 1011b(-21d) | 1111 0101b(-11d) |
-128 | 1000 0000b | 1100 0000b(-64d) | 1110 0000b(-32d) |
2.2.2 左移
负数左移,低位补0,高位移出舍弃,由于低位总是补0而符号位也参与移位,因此有符号类型负数左移可能变为非负数;对于无符号类型,只有数据域无符号位,低位补0,高位移出舍弃。
int8_t | 补码 | << 1 | << 2 |
---|---|---|---|
-1 | 1111 1111b | l111 1110b(-2d) | l111 1100b(-4d) |
-42 | 1101 0110b | 1010 1100b(-84d) | 0101 1000b(88d) |
-128 | 1000 0000b | 0000 0000b(0d) | 0000 0000b(0d) |
3算术移位和逻辑移位
上面讲的移位规则其实可以分为两类,算术移位和逻辑移位。
3.1 算术移位
对于有符号数进行的移位称为算术移位 ,算术移位需要考虑符号位。
- 算术移位右移时,非负数高位补0,负数高位补1,因此算术右移符号位不变,低位移出舍弃;
- 算术移位左移时,不论正负数低位都补0,因此算术左移可能改变符号位,高位移出舍弃;
3.2 逻辑移位
对于无符号数进行的移位称为逻辑移位 ,无符号位。
- 逻辑移位右移时,高位补0,低位移出舍弃;
- 逻辑移位左移时,低位补0,高位移出舍弃;