二进制位操作

计算机系统基础实验1(二进制操作)

一、知识储备

在具体实验之前,放一些知识储备和实验要求,以备更好地理解实验原理,更好地了解二进制操作,有了这些基础那么写实验就可以放飞思路了。

A.补码

  1. 所有的数据在计算机中都是以补码形式储存的,正数不必多说,负数是将一个数的原码取反加1,最高位为符号位
  2. 对于补码来说,符号位只是判断一个数是正是负的一种方法,其实符号位也是负数进行运算的一位,补码的意思在于用模加上负数,用实际上没有符号位的数代表负数,例如模12,从12点拨到9点,往回拨3(-3),就相当于顺拨9,对于int型,32位,模就是2^8,那么-3的补码就是2^8-3,(取反加1即将2^8拆开,0xffffffff-3+1,0xffffffff-3就是取反)
  3. 对负数的补码的任何操作都是直接的,而不是会转化成原码再操作,而且都是有效的(除了会溢出的情况),比如-3+2,即2 ^ 8 - 3 + 2 = 2 ^ 8 - 1,又如-3 + 4,即2 ^ 8 - 3 + 4 = 1(最多保存32位,模掉2^8,结果依然是对的,补码的意义就在这,将减法转化为加法),左移右移同理

B.溢出

  1. 定义:计算机的数据类型的位数是一定的,只能表示确定的位数n,一旦两个同号数相加或异号数相减,计算结果有可能超出n位数可表示的范围,超出可表示范围叫溢出,那么结果>2^(n-1)-1,或<2^(n-1),就会出现溢出
  2. 此外由于规定最高位为符号位,溢出的结果可能是正数相加变成了负数,负数相加变成了正数
  3. 而同号数相减或异号数相加时,这种情况舍弃最高位并不影响运算结果,舍去最高位相当于取模操作,得到的是正确的结果,比如3中的-3+4

C.最低最高有效位

以一个二进制位为单位

LSB:  least significant bit,即权值最低的一位

MSB: most significant bit,即权值最高的一位,或者说符号位

以字节为一个排列单位(1 byte = 8 bit)

LSB:  least significant byte,权值最低的8位

MSB: most significant byte,权值最高的8位

D.左移右移

正数左移,左边补0,负数左移左边补1,右移,正负数都是补0,是为了保证数据的正确性;而unsigned型,因为没有符号位,所以左移右移都是补0

  • 实验操作

I.位操作

1.lsbzero(将x的LSB清零)(以二进制为单位的lsb)

int lsbZero(int x) {

    //method 1

    /* x = x >> 1;

     x = x << 1;*/

    //method 2

    //x = x & ~1;    

    //method 3

    x = ~ x ;

    x = x | 1;       

    x = ~ x;        

    return x;

}

[方法1]

将x右移一位,再左移一位

[方法2]

将x与最低位为0,其余位全为1的数按位与

[方法3]

将x取反,最低位与0x1按位或,再对x取反

  1. byteNot

将x的第n个字节取反(字节从LSB开始到MSB依次编号为0-3)

(以字节为单位的lsb、msb)

int byteNot(int x, int n) {

     n<<=3;          //n*8

     x=x^(0xff<<n);   

     return x;

}

1^0=1,1^1=0,即用1^x可实现取反操作

【方法1】

为实现对字节取反,为使左移位数为8的倍数,将n<<3,实现n*8,因1^1=0,1^0=1,0xff<<n再与x异或,就可实现对x第n个字节取反

【方法2】

提取~x中第n个字节的部分存到add,再将原来的x第n个字节部分全部置为0,再加上add

  1. byteXor

比较x和y的第n个字节(字节从LSB开始到MSB依次编号为0-3),若不同,则返回1;若相同,则返回0

int byteXor(int x, int y, int n) {

    n<<=3;

    /*method 1

    x>>=n;x<<=24;

    y>>=n;y<<=24;

    //method 2

    x=(x>>n)&0xff;

    y=(y>>n)&0xff;*/

    //method 3

    x&=(0xff<<n);

    y&=(0xff<<n);

   return !!(x^y);

思路:将n*8,然后将x、y除第n个字节外清0,再将x、y按位异或,1^x,将x取反,0^x,x不变

【方法1】

将x,y分别把第n个字节移到最右端,在移到最左端,其右端全为0,二者异或,相同则结果为0,否则不为0,用!符号将结果转化为1/0

【方法2】

同方法1,只是将x非比较字节清0方法不同,将x第n个字节移到最右端,用x&0xff的方法将其左端清0

【方法3】

将x非比较字节清0方法不同,不移动x、y,将0xff移到第n个字节,在按位与,即可实现清0

  1. logicalAnd(x&& y)

int logicalAnd(int x,int y){

return !!x & !!y;

【方法1】

用!!将x、y转化,非0转为1,0转为0,再按位与即可

  1. logicalOr(x|| y)

int logicalOr(int x, int y)

{

    // method 1

    return !!x | !!y;

    // method 2

   return !!(x|y);//同0为0,否则按位或后不为0

}

【方法1】

用!!将x、y转化,非0转为1,0转为0,再按位或

【方法2】

x|y,x和y都是0,x|y为0,若有一个非0,则x|y结果不是0,与x||y结果一致

  1. rotateLeft(将x循环左移n位)

int rotateLeft(int x, int n) {

    int a=x;

    x<<=n;

    // a>>=(32-n);    

  1. >=32+(~n+1);   

a&=~(~0<<n);

    return x+a;

}

提示:32 - n = 32 + ( ~ n + 1) = 0xff ^ n + 1

对一个数取补码就得到他的相反数,32-n就是0xff-n+1,0xff-n就是将n取反

思路:a = x,将x左移n位,将a右移32-n位,将a除低n位外清0,再返回x+a即可

【方法1】

a=x,x左移n位,将a右移32-n位,左将并端清0,x+a即为所求

【方法2】

对于32-n有两种做法,一是32+n的补码,二是31-n+1,31-n也就是0x1f^n

  1. parityCheck(若x有奇数个1,则返回1;否则,返回0)

int parityCheck(int x) {

    /*

    method 1

    x^=x<<16;     

    x^=x<<8;

    x^=x<<4;

    x^=x<<2;

    x^=x<<1;

    x>>=31;

}

思路1:将x分成两半,将低位的一半和高位的一半异或,异或结果相同为0,不同传为1,再将高位的一半再分成两半,再相异或,直至异或的结果传到最高位上,这时最高位上即为所有偶数个1消掉后的结果;或者反方向传到最低位上

思路2:将x两个两个分,将异或结果传到偶数位上,舍弃奇数位,假设将偶数位的数当成新的数组,并再次将异或结果累加到偶数位上,重复操作,直至异或结果传到最高位;也可反方向传到最低位

int parityCheck(int x) {

//method 2

    x^=x<<1;

    x^=x<<2;

    x^=x<<4;

    x^=x<<8;

    x^=x<<16;

    x>>=31;

}

II.补码运算

8.mul2OK(计算2*x,如果不溢出,则返回1,否则,返回0)

int mul2OK(int x) {

    //method 1

    x^=(x<<1);

    x>>=31;

    //x=0x11111111 or x=0x00000000

x+=1;

   /* method 2

    x=( (x>>31) & 1 ) ^ ( (x>>30) & 1) ^ 1;*/

    return x;

}

思路:

32位数可表示的范围是-2^31~2^31-1

将x*2即将x<<1,看x<<1后是否溢出,只需看x二进制前两位

若前两位为11,x >= -(2^30),x*2>= -(2^31),在范围内;

若前两位为00,x <= 2 ^ 30 - 1 ,x*2<= 2^31 - 2,在范围内;

若前两位为10,则x<= - (2^30+1)(0xbffffff),x*2 <= - (2^31+2),超出范围;

若前两位为01,则x>=2^30,x*2>=2^31,超出范围

注:对于负数的补码左移一位,不溢出情况下即乘2的证明

Eg.-3<0,则-3补码为(2^32-3),左移一位即(2^32-3-2^31)*2=2^32-6

【实现方法1】

将前两位异或,结果移到最右端,根据逻辑移位规则,若移位后x=0x11111111,即为溢出,溢出返回0,因x+1=0,同时x=0x0时,说明未溢出,需返回1,x+1=1,故考虑到两种情况,均返回x+1即可

【方法二】

同理,判断前两位是否为10或01,将x的最高位和次高位分别移到最右端,左边清0后,将这两位异或,溢出该位为1,否则为0,为实现溢出返回0,否则返回1,将结果用1^x,取反

  1. mult3div2(计算(x*3)/2,朝零方向取整)

int mult3div2(int x) {

    x+=(x<<1);

    int lsb=x&1;

    int msb=(x>>31)&1;

    int y=(x>>1)+(msb & lsb);

    return y;

}

    //method 2
    x+=(x<<1);
    x+=1&(x>>31);
    x>>=1;
    return x;

思路:x*3用x+=(x<<1)来表示,不考虑溢出的情况,但要考虑朝零方向取整,也就是除2后的数要舍去余数,-5/2=-2,5/2=2,而对于>>1,不一定是除2,因为>>1是舍掉了lsb(最低有效位),然后将剩下的除2,对于奇数,相当于减1后除2,而对于正数是没问题的,但对于负数,如:-3(2^32-3),就是(2^32-4)/2+2^31=2^32-2,就是说-3>>1=-2,显然不符合要求,故需对负奇数>>1后的结果加1

【实现方法1】

当最高最低位都为1时,为负奇数,将除2的结果加1

【方法2】

获取x*3后的最高位,如果x是负数,则加最高位即加0x1,负奇数将将变为比他大1的负偶数,-5+1= - 4,-4/2=-2,实现向上取整,对负偶数无影响;如果x是正数,加最高位即加0

  1. subOK(计算x –y,如果不溢出,则返回1,否则,返回0)

int subOK(int x, int y) {

    int a=(x>>31)&1;

    int b=(y>>31)&1;

    y=~y+1;

    int c=((x+y)>>31)&1;

    return !((a^b)&(a^c));

}

思路:讨论两个数相减溢出的情况,两数同号绝不会溢出,两数异号可能溢出,溢出的情况就是正负数超出表示范围,两个异号数相减就是2个正数相加或2个负数相加,当x与x-y符号不同时,即发生溢出,也就是数据影响到了符号位,而且这里并不是舍弃最高位得到正确结果的情况,参照知识储备

思路2:

根据溢出标志OF=Cout^Cn-1,提取Cn-1,再提取x和y的最高位,将三者相加,进位即Cout,

OF=1就溢出,溢出返回0,用!转化即可;同时对于y=0x80000000时,因其补码还是其本身,但一个非负数减0x80000000,结果会溢出,但OF=0,OF判断不溢出,一个负数减0x80000000,结果不会溢出,OF=1,OF判断溢出,故考虑y=0x80000000的情况,对这种情况的溢出判断取反即可

//  method 2
    y=~y+1;
    int cn=x&0x7fffffff;
    cn+=(y&0x7fffffff);
    cn>>=31;
    cn&=1;
    int a=1&(x>>31);
    int b=1&(y>>31);

    int c=!(cn^((a+b+cn)>>1));
    int d=!(0x80000000^y);
    return c^d;
 

10.absVal(求x的绝对值)

int absVal(int x) {

    /*method 1

    int a=x>>31;

x=(a^x)+(1&a);*/

    // method 2

    int a=x>>31;

    x=(a&(~x+1)) + (~a&x);

    return x;

}   

思路:求x绝对值,对负数取绝对值即可(对负数取反加1)

Method 1:利用x的符号位和异或的性质,a=x>>31,

对负数,0xffffffff^x=~x,1&0xffffffff&1=1;

对正数,0x00000000^x=x,0x00000000&1=0,实现同样的操作因a的不同,操作不同;

Method 2:分类讨论,用0xffffffff或0x00000000,实现是否需要取反

III.浮点数操作

11.float_abs(返回浮点数‘|f|’的二进制表示,当输入参数是NaN时,返回NaN)

 unsigned float_abs(unsigned uf) {

          int m=uf&0x7fffffff;

          /*method 1

          if(m>0x7f800000)

             return uf;

          */

          //method 2

          if(! ((m>>23) ^ 0xff)  &&  (uf&0x007fffff) )

             return uf;

          else

             return m;

}

注:需要返回的是unsigned,意思是只需要把符号位变一下就行了,输入参数为NaN(全1阶码,非0尾数时)返回NaN

思路:将浮点数符号位置为0,判断阶码是否为NaN;另外unsigned型左移补0,故直接判断阶码是否全1时要注意方法

float_f2i(返回浮点数‘f’的强制整型转换“(int)f”表示)

 */

int float_f2i(unsigned uf) {

    int j=(uf>>23)&0xff;

    int w=uf&0x007fffff;

    int i=w+0x00800000;

    if(j>158) return 0x80000000u;

    if(j<127) return 0;

    else{

          if((uf>>31)&1){

               if(j<150)   return ~(i>>(150-j))+1;

               else       return ~(i<<(j-150))+1;

          }

         else{

              if(j<150)   return i>>(150-j);

              else       return i<<(j-150);

         }

    }

}

思路:看起来复杂,其实很简单,就是2次分类讨论

1、考虑超出范围的情况

首先因为浮点数表示范围比int型大,所以对于超出int范围的要返回0x80000000u;

取阶码j,j-127>30时,因为int型是32位,而且需要有符号位,而浮点数尾数前隐藏了一位,所以阶码-127后最大是30,即尾数*2^30,才能正确表达int型;

2、考虑0.xxxxx的情况

而对于j-127<0,就会导致float是0.xxxxx,转变为int型后是0

3、Else  (127<=j<=157)

int i=尾数前加个1

当j - 127<23,i右移,j-127>23,i左移

  1. 考虑如果i为负数将i取反加1

下面是实验报告要求写的问题讨论,有些意思,就附在下面了

问题讨论:

1.对二进制进行位操作,而不允许使用各种整型/无符号整型操作,是难点所在,因为这意味着不能直接采用乘号、除号和减号,if-else语句,也不能通过加负号的方式对一个数去相反数,而这也正是位操作有意思的地方,因为位操作可以完美实现上述功能

2.通过二进制的位操作,我逐渐意识到,用补码表示二进制数的含义和优点,采用补码,可将负数-m用一个2^n-m的二进制数表示,这样就将减法转化为加法的形式,而在结果的真值在-2^n~2^n-1的范围内时,其结果都是正确的,尽管可能会有舍掉最高位的情况,但正数加负数,结果可能舍掉最高一位1,即减去2^n,但结果为正确值;对一个数取补码,即可得到它的相反数,这是二进制的约定

3.符号位是衔接各种操作的纽带,通过符号位的变化即可判断两数相加是否存在溢出,还可通过负数的符号位是1,正数的符号位是0,与其他操作结合实现对一个数取绝对值等操作

4.使用二进制位操作,也要考虑到二进制所存在的一些缺点,比如可能存在溢出现象,这可能会导致计算机在计算过程中因为数据范围问题,导致计算出现错误;除此外,右移运算符,还会使最低有效位被丢掉

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悠悠水云身

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值