世界上有两种人,懂和不懂二进制的。最近发现自己之前算是不懂的。
一、整数表示
假设整数数据类型是W位,则写成二进制位x→是[Xw-1,xw-2,……x0]。
1. 无符号整数
对于无符号整数没有符号位,所以上述二进制向量x→所表示的无符号整数就是
例如W=4时,[1010]就是1*23 +0*22 +1*21+0*20=8+0+2+0=10
2.有符号数
当作为有符号数时,最高有效位Xw-1 称为符号位,解释为负权,
所表示的有符号数就是
例如W=4时,[1010]就是-1*23 +0*22 +1*21+0*20=-8+0+2+0=-6
2.1当Xw-1 为0时,表示为一个正数,与无符号整数相同。
2.2当Xw-1 为1时,表示为一个负数,因为这个负权值是-2w-1 ,2w-1的无符号表示就是(10……0),1后面跟w-1个0,所以它的绝对值大于任意的组合,所以此时的B2Tw( x→)一定是一个负数,
二、C中的整数转换
在c语言中,无符号数和有符号数的转换,原则上是基本的位表示保持不变。
这里之所以说是在C语言中,因为在翻译为汇编后,汇编语言则是纯粹的位操作,没有像C语言这样具有符号概念。
转换包括显示或者隐式转换,注意这里的“位表示”表示不变!!
例如int x=0xA,
unsigned int ux1=x;
unsigned int ux2= (unsigned in)x;
执行完成后 ux1 和ux2 均是0xA,
C中如果一个表达式包含有符号和无符号数,那么C会将有符号数强制转换为无符号数,这一点非常重要,
例如表达式-6<0U,返回值是0,因为这里会将这个表达式作为无符号处理,-1,是0xA,而作为无符号则是10。
三、扩展与截断
1.扩展,
分为零扩展和符号扩展。
如2位的数字 (10)2 扩展成四位时,
零扩展只是简单的在开头添加0,对于无符号数就是这样扩展为0010
符号扩展则是在开头添加原来最高位的值,对于有符号数就是这样扩展的1110。
例如i386 linux下c语言
TYPE c=0xFF;
int x=c;
unsigned int ux=c;
当TYPE是char时,x、ux均为0xFFFFFFFF,汇编格式均使用了movsbl,
当TYPE是unsigned char时,x、ux均为0x000000FF,汇编格式均使用了movzbl
所以在 = 扩展时,gcc编译器根据等号右边操作数的属性,决定如何扩展存入左边操作数所在的存储器。
可以考虑TYPE是char时,unsigned int ux= (unsigned char)c;ux的值~~!
2.截断
截断则注意一个原则,截断剩下的位向量保持不变,
即 是[Xw-1,xw-2,……x0]截断为一个k位时,得到一个位向量 是[Xk-1,xk-2,……x0]
四、整数运算
大多数计算机中,无论是有符号还是无符号,除了右移操作和特殊的算术操作即双操作数乘除法指令,
在翻译成的汇编代码中加减乘除和左移都是一样的指令,右移则根据操作数是否有符号选择算术还是逻辑右移。
整数运算最麻烦的也许就是对溢出的理解了。
1. 加法,
记住有符号和有符号使用用一种算术指令。
因为二进制补码和无符号加法运算有着完全相同的位级表示,而且通过模数加法的属性,可以证得。
1.1无符号
考虑两个无符号数ux,uy;两者之和是sum;
如果sum超过了unsigned(-1),-1的二进制向量位全为1,也就是无符号的最大表达值。
由于我们这种二进制表示有一种环属性,
当超过这个unsigned(-1)时,由计算机得到的结果是sum- 2w
1.2有符号
它存在两种溢出,正溢出和负溢出。
有符号数最大值是01……1,即2w-1 -1
有符号数最小值是10……0,即-2w-1
负溢出会是结果成为正,正溢出会是结果成为负。
两个有符号数x,y和sum,
如果sum>=2w-1 则正溢出,计算机结果是sum-2w
如果sum<-2w-1 则溢出,计算机结果是sum+2w
2. 乘法
同样二进制补码和无符号乘法运算有着完全相同的位级表示
因为W位的二进制数,成绩结果可能需要超过W位到2W位的位级表示
但是在计算机乘法运算中,将结果截断为W位,所以会在某些大数乘积运算时差生溢出。
例如IA32 linux下
int x=0xFFFF;
int y=0xFFFF;
x*y结果则是0xFFFE001,差生一个负数。
因为在大多数及其上,乘除指令相当的慢,需要更多的时钟周期,所以对于带有常数因子的运算,编译器会试着用移位和加法运算的组合来代替。
ps:
1.表达式的输入好麻烦
2.二进制表示整数以及运算还有着很多特别的属性,当然都能从以上推导出来,
3.如果希望更深入的理解二进制,可以去看些汇编代码会更好。