数据在计算机中都是二进制存储的,都可进行按位运算,至于使用的何种编码规则,运算后的结果就不同了。在微机中存储数据有定点表示法和浮点表示法两种。定点表示法是将小数点固定在某一位置,通常有纯小数和纯整数两种方法。我们平时处理的整数就是纯整数这种表示,比于int,char,long类型等。浮点表示法主要用来表示实数,在C/C++中用来表示float和double型。本文主要针对纯整数的位运算,对于计算机、电子和控制等与计算机硬件相关专业的同学是必须掌握的。
1.
C/C++语言中的运算符有如下几个:
<<
(1)
按位求反运算符 ~
(2)
左移运算符 <<
右移运算符 >>
(3)
按位或运算符 |
按位异或运算符 ^
按位与运算符 &
2.
位运算牵涉到数据的编码问题,计算机中所有数据都是二进制编码表示,对于带符号数在微机中通常有原码、反码和补码表示,表示的数的范围与数据使用的位数有关。设表示一个数的二进制的位数为n,通常为8,16,32,64等,在C/C++中整数为 字符型char 型8位,短整型short int 为16位,整型int和长整型long为32位。如果没有特别说明即未加关键字unsigned时均为带符号数。下面以短整型带符号数来说明原码、反码和补码的表示,其余的表示可以类推得到,为了后面举例方便,对无符号数也进行了解释。
(1)
原码表示法中最高位(第n-1位)为符号位,正数为0,负数为1,其余n-2…0位为二进制真值,如表1所示。
表1 带符号的短整型数原码表示
十进制数 | 原码表示 |
+0 | 00000000 00000000 |
+1 | 00000000 00000001 |
+2 | 00000000 00000010 |
… | … |
+32767 | 01111111 11111111 |
-0 | 10000000 00000000 |
-1 | 10000000 00000001 |
… | … |
-32766 | 11111111 11111110 |
-32767 | 11111111 11111111 |
从表1可以看出,带符号的短整型原码表示法,共表示了65535个数,其中0有两个,有+0和-0。
如+25与-25的表示如下:
十进制数 | 原码表示 |
+25 | 00000000 00011001 |
-25 | 10000000 00011001 |
也可以表示成十六进制数0x0019和0x8019。
原码表示法简单并易于理解,与真值之间的转换也很方便,这是它的优点,缺点是进行加减运算时比较麻烦,加减运算时要考虑符号,比如25+(-26)=-1,如何才能得到呢?
+25
-26
是错误的,所以两个数相加且异号时必须比较两个数的绝对值大小才能确定实际的被减数,上例中-26为被减数,+25为减数,结果才能正确。
(2)
反码表示法可用原码来定义,设数X的原码表示法为[X]原,[X]反表示X
[X]反=[X]原
[X]反=~[X]原
从公式(1)的定义可知反码表示法最高位同样是符号位,为0表示正数,为1表示负数,且有+0和-0之分。[+0]反=00000000 00000000,[-0]反=11111111 11111111。
例:+25和-25的补码表示
[+25]反=[+25]原=00000000 00011001
[-25]反=
即-25的反码表示为+25的原码表示按位取反所得的值。
(3)
设[X]补表示数X的补码表示法,则定义如下:
[X]补=[X]原
[X]补=~[X]原
按定义可以得到短整型数的表示如表2所示。
表2 带符号的短整型数补码表示
十进制数 | 补码表示 |
+0 | 00000000 00000000 |
+1 | 00000000 00000001 |
+2 | 00000000 00000010 |
… | … |
+32767 | 01111111 11111111 |
-32768 | 10000000 00000000 |
-32767 | 10000000 00000001 |
… | … |
-2 | 11111111 11111110 |
-1 | 11111111 11111111 |
负数的补码表示还可用下列公式表示:
[X]补=2n+X
由式(2)中的定义求补码的方法如下:
正数的补码即为其原码表示,负数的补码为先求出其反码,再在最低位加1。
例如[25]补和[-25]补的求解过程如下:
[25]补=[25]原=00000000 00011001
[-25]补= [-25]反+1=
由公式(3)求补码的方法更为简单,但是必须知道数的表示位数n,过程如下:
[-25]补=2n-25=0x10000-0x0019=0xffe7
补码运算有两个公式,如式(4)所示。
[X+Y]补=[X]补+[Y]补
[X-Y]补=[X]补+[-Y]补
从式(4)可以看出,两个数相加后的补码表示等于两个数补码之和,两个数相减的补码表示等于被减数的补码加上减数的相反数的补码数之和,即减法变成加法运算(在计算机硬件实现中这一点非常容易)。例如:
[25-26]补=[25]补+[-26]补
=0x0019+0xffe6
=0xffff
=[-1]补
(4)
无符号数最高位为有效的数据信息,表达的数据范围如表3所示
表3 带符号的短整型数补码表示
十进制数 | 补码表示 |
0 | 00000000 00000000 |
1 | 00000000 00000001 |
2 | 00000000 00000010 |
… | … |
32767 | 01111111 11111111 |
32768 | 10000000 00000000 |
32769 | 10000000 00000001 |
… | … |
65534 | 11111111 11111110 |
65535 | 11111111 11111111 |
3.
(1)
它的优先级高于四则运算,它是按位对操作数求反,单目运算,从左至右结合。
设有如下程序段:
short int a=-25,b;
unsigned short c;
b=~a;
对于无符号数来说,按位取反仍然是无符号数;对于带符号数来说,负数按位取反就变成了正数。程序段运行结果如下:
~(-25)=24
分析结果:
由前面得知[-25]补=0xffe7=11111111 11100111
按位取反等于
(2)
它的优先级高于关系运算,也是单目运算,从左至右结合。
格式:
<操作数> >>N
<操作数> <<N
左移1位操作数扩大一倍,即相对与乘2,右移1位操作数缩小1倍,相当于除2。
对于无符号数来说,左移时各位依次左移1位,最低位补0;右移时各位依次右移1位,最高位补0。
对于带符号数来说,左移时各位依次左移1位,最低位补0;右移时各位依次右移1位,最高位不变(补符号位)。
#include <stdio.h>
void main()
{
}
运行结果
-32767<<1=2
60000>>1=30000
分析结果:
a为带符号的补码表示的数,[-32767]补=10000000 00000001=0x8001,左移一位后
[-32767]补<<1= 0000000 000000010=0x0002
最高位为符号位,本来是1表示负数,左移一位后,1移入到标志位CF(标志寄存器中的1位)中,结果变成了正数。
无符号数c各位依次右移一位后,最高位补0,结果等于除2。
带符号数右移时,除了各位要依次右移一位外,最高位补符号位,以保证正确性。
实际上左移一位各位权值加倍,右移一位各位权值减少一半。
(3)
按位逻辑运算符有按位或运算符| 按位异或运算符 ^ 和按位与运算符&,都是双目运算符,自左至右结合。
设有如下程序段:
short int x=-1124,y=2094,z1,z2,z3;
z1=x|y;
printf("%d|%d=%d\n",x,y,z1);
z2=x&y;
printf("%d&%d=%d\n",x,y,z2);
z3=x^y;
printf("%d^%d=%d\n",x,y,z3);
运算结果为:
-1124|2094=-1090
-1124&2094=2060
-1124^2094=-3150
分析结果如表4所示。
表4 或与异或位运算结果分析表
变量 | 十进制数 | 短整型补码表示(二进制) | 短整型补码表示(十六进制) |
X | -1124 | 11111011 10011100 | 0xfb9c |
Y | 2094 | 00001000 00101110 | 0x082e |
z1=x|y | -1090 | 11111011 10111110 | 0xfbbe |
z2=x&y | 2060 | 00001000 00001100 | 0x080c |
z3=x^y | -3150 | 11110011 10110010 | 0xf3b2 |
(4)
位运算符与赋值运算符可以组成复合赋值运算符。
例如: &=, |=, >>=, <<=, ^=等
例:
本文以短整型数为例,介绍了在微机中数的表示特别是带符号数的表示,介绍了位运算符的运算,并对实例结果进行了分析。