CSAPP lab1——位运算

本次为一次计算机系统实验,就是使用一些基本的运算符来实现函数功能。git

ps作这些题让我想起大一上学期刚学二进制时被鹏哥支配的痛苦。express

知识准备:函数

1.负数等于正数取反加一。ui

2.左移一位至关于将这个数扩大两倍,右移两位至关于将这个数缩小两倍spa

3.符号位向右移动后,正数补0,负数补1。设计

4.负数补码最高位是1,正数补码最高位是0code

5. 32位正数取值为blog

最大:01111111 11111111 11111111 11111111ci

最小:10000000 00000000 00000000 00000000it

min:-2147483648  max:2147483647

 

 

1.

/* * bitXor - 仅容许使用~和&来实现异或 * 例子: bitXor(4, 5) = 1 * 容许的操做符: ~ & * 最多操做符数目: 14 * 分值: 1 */

解题思路:简单的异或,a⊕b = (¬a ∧ b) ∨ (a ∧¬b)但要求使用&,因此须要德摩根律

int bitXor(int x,int y) { return ~(~(~x&y)&~(x&~y)); }

 但这样还不是最简单的,若是使用同或的非来表示异或,a⊕b = ¬((a ∧ b) ∨ (¬a ∧¬b))还能再少用一个字符。

int bitXor(int x, int y) { return ~(x&y)&~(~x&~y);//同或的非 }

 

2.

/* * tmin - 返回最小的二进制补码 * 容许的操做符: ! ~ & ^ | + << >> * 最多操做符数目: 4 * 分值: 1 */

解题思路:最小值为0x8000 0000,咱们能够将1左移31位获得最小值。

int tmin(void) { return 1<<31; }

 

3.

/* * isTmax - 若是x是最大的二进制补码,返回1;不然,返回0 * 容许的操做符: ! ~ & ^ | + * 最多操做符数目: 10 * 分值: 2 */

解题思路:最大的二进制补码为0x7FFFFFFF,为判断输入是否为这一个数,咱们只须要将其与最小的二进制补码与或一下判断是否为0便可。

int isTmax(int x) { return !(x^~(1<<31)); }

 

4.

/* * negate - 返回-x * 例子: negate(1) = -1. * 容许的操做符: ! ~ & ^ | + << >> * 最多操做符数目: 5 * 分值: 2 */

解题思路:正数取反加一即为负数。

int negate(int x) { return (~x+1); }

 

5.

/* * allOddBits - 若是全部奇数位都为1则返回1;不然返回0 * 例子: allOddBits(0xFFFFFFFD) = 0, allOddBits(0xAAAAAAAA) = 1 * 容许的操做符: ! ~ & ^ | + << >> * 最多操做符数目: 12 * 分值: 2 */

解题思路::只有全部奇数位为1的数,与0x5555 5555进行&运算才会获得0。5->(0101)。故而须要获得0x5555 5555,将0x55(01010101)分别左移八、1六、24获得3个数,而后将这三个数相加便可获得0x5555 5555。

int allOddBits(int x) { return !(~(x|(85+(85<<8)+(85<<16)+(85<<24)))); }

 

6.

/* * isAsciiDigit - 若是x是ascii码中的0~9,返回1;不然返回0 * 例子: isAsciiDigit(0x35) = 1. * isAsciiDigit(0x3a) = 0. * isAsciiDigit(0x05) = 0. * 容许的操做符: ! ~ & ^ | + << >> * 最多操做符数目: 15 * 分值: 3 /* 

解题思路:若x是数字,则x在‘0’~‘9’之间。能够用x-48>=0和x-58<0(x+~48+1>=0和x+~58+1<0)来计算。

int isAsciiDigit(int x) { return !((x+~48+1)>>31)&!!((x+~58+1)>>31); }

 

7.

/* * isLessOrEqual - 若是x<=y返回1不然返回0 * 例子: isLessOrEqual(4,5) = 1. * 容许的操做符: ! ~ & ^ | + << >> * 最多操做符数目: 24 * 分值: 3 */

解题思路:

直接用y-x可能会超出int的表示范围,故而:当x与y同号时,转换为p=y-x>=0,而后p符号位(p>>31)&1为0则返回1,符号位1则返回0;异号时,只要x>=0,就要返回0,不然返回1,由(x>>31)&1能达到该效果。c=a(x>>31)+b(y>>31)可做为x,y同号异号的判断,异号的时候为1和0,相加为1;同号的时候为1和1或者0和0,相加得0或2,取反以后必为非0。

int isLessOrEqual(int x, int y) { int a=x>>31; int b=y>>31; int c=a+b; int p=y+(~x+1);///p=y-x int q=!((p>>31)&1);///若p>=0,则q为1,成立;反之,q为0,不成立。 return (c&(a&1))|((~c)&q);///若为异号,c为1,若a为1,说明x<=y,成立;反之说明x>y不成立。 ///若为同号,c为0或者2,取反后为非0,若q为1,相与后必为非0。  }

 

8.

/* * conditional - 实现x?y:z * 例子: conditional(2,4,5) = 4 * 容许的操做符: ! ~ & ^ | + << >> * 最多操做符数目: 16 * 分值: 3 */

 解题思路:当x不为0时,!x=0,函数结果为y。这时候须要(0xffff ffff)&y|(0x0),来保存y。 当x为0时,!x=1,函数结果为z。这时候须要(0x0)|(0xffff ffff)&z,来保存z。

int conditional(int x, int y, int z) { return ((!x+~1+1)&y)|((~!x+1)&z); }

 

 9.

/* * absVal - absolute value of x * Example: absVal(-1) = 1. * You may assume -TMax <= x <= TMax * Legal ops: ! ~ & ^ | + << >> * Max ops: 10 * Rating: 4 */

解题思路:对整数取绝对值,咱们知道int类型的实数,正负数的区别在于最高位是0仍是1,正数最高位是0,负数最高位是1。正数右移31位后为0,负数右移31位后为-1(负数右移高位补1)。而负数要变为正数,咱们知道须要-1再取反。那么咱们就能够利用这两个性质来作题了。

/*

正数右移31位后为0,负数右移31位后为-1(负数右移高位补1)
异或1取反
异或0不变
负数要变为正数须要+1以后取反

*/

int absVal(int x) { int t = (x>>31); return (x+t)^t; /*正数右移31位后为0,负数右移31位后为-1(负数右移高位补1)*/ }

10.

/* * bitCount - returns count of number of 1's in word * Examples: bitCount(5) = 2, bitCount(7) = 3 * Legal ops: ! ~ & ^ | + << >> * Max ops: 40 * Rating: 4 */

解题思路:这题应该是此次做业里最难的题了,只用40次操做,而且不能用循环和if,计算x二进制表示中1的个数。首先须要设计5个常数,分别为0x55555555,0x33333333,0x0f0f0f0f,0x00ff00ff,0x0000ffff。

为何这么设计呢,能够把5个数字的二进制写出来,分别是间隔1个0,2个0,4个0,8个0,16个0

而后下面的计算方法,和间隔对应,分别右移1,2,4,8,16次。如何理解呢,若是只给你一个只有2位的二进制数x,以及01,怎么计算里面1的个数呢,是否是作(x&1)+((x>>1)&1)呢?经过移位,把高位的1,移到低位求和。32位的数字就能够当作是16个2位的,以后就能够等价看做,一个16位的数字,分红8段,作上面第二个计算操做。总的来看这个操做就是把高位的1往低位移动,而后相似分治,分红多段的移动。很是nice的一个题哦。关于这道题的一些理解,32位数字当作16个2位的,两个相邻位置做为一组进行一次计算统计1的个数,而后进行合并;再以后四个相邻位置做为一组进行一次计算统计1的个数;以后八个,十六个,三十二个做为一组。

int bitCount(int x) { int mask1 = 0x55; int mask2 = 0x33; int mask3 = 0x0f; int mask4 = 0xff; int mask5 = 0xff; mask1 += mask1<<8; mask1 += mask1<<16;//generate 0x55555555 mask2 += mask2<<8; mask2 += mask2<<16;//generate 0x33333333 mask3 += mask3<<8; mask3 += mask3<<16;//generate 0x0f0f0f0f mask4 += mask4<<16;//generate 0x00ff00ff mask5 += mask5<<8; //generate 0x0000ffff x =((x>>1)&mask1) + (x&mask1); x =((x>>2)&mask2) + (x&mask2); x =((x>>4)&mask3) + (x&mask3); x =((x>>8)&mask4) + (x&mask4); x =((x>>16)&mask5) + (x&mask5); return x; }

 

 11.

/* * byteSwap - swaps the nth byte and the mth byte * Examples: byteSwap(0x12345678, 1, 3) = 0x56341278 * byteSwap(0xDEADBEEF, 0, 2) = 0xDEEFBEAD * You may assume that 0 <= n <= 3, 0 <= m <= 3 * Legal ops: ! ~ & ^ | + << >> * Max ops: 25 * Rating: 2 */

解题思路:须要咱们将字节交换,主要这里交换的是字节而不是位!咱们知道int型有4个字节,每一个字节有8位,这里咱们就以8位为一组组成字节进行总体交换。思路很简单,咱们先将须要交换的字节转移到交换后的位置,在这个过程当中不可避免的会夹带着其余的字节的变化,先不去管,再得到一个排除须要交换字节的原数(须要交换的字节的位置全是0),最后三者一与便获得交换后的数了。

int byteSwap(int x, int n, int m) { int a = n<<3; int b = m<<3;//乘以8,由于8位一个字节,投射到该字节对应的位上 int step1 = ((x>>a)&0xff)<<b; int step2 = ((x>>b)&0xff)<<a; // int step3 = x&~(0xff<<b)&~(0xff<<a); int step3 = x&(~((0xff<<a)|(0xff<<b))); return (step1|step2|step3);//将须要交换的字节扣除来,错位交换 }

 

/*
 * oddBits - return word with all odd-numbered bits set to 1
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 8
 *   Rating: 2
 */

解题思路:获得int型32位上一个奇数位全是1的数,那么就是0xaaaaaaaa(1010),那么最简单的方法就是经过0x55构造就能够了。注意这里的第0位安排不是奇数位,安排到了偶数位上了。

int oddBits(void)
{
    int a=0xaa;//1010 1010
    a=a|(a<<8);
    a=a|(a<<16);
    return a;
    // return 0xaaaaaaaa;?
}

 

 

 12.

/* * isLess - if x < y then return 1, else return 0 * Example: isLess(4,5) = 1. * Legal ops: ! ~ & ^ | + << >> * Max ops: 24 * Rating: 3 */

解题思路:一开始的思路就是用x-y看看获得的最高位是1仍是0,是1则是负数说明x<y,是0则是正数说明x>y,并且获得的最高位刚好是返回值。可是有这么一个问题,可能会出现溢出啊,int类型存不下!我在这里停了很久,也试着用long long 来存数,可是只能使用int类型来进行运算,不能使用其余类型的数,不能进行强制类型的转化。

其实咱们能够把那些会形成溢出的数给分离出来,咱们知道正数减负数和负数减正数才可能会形成溢出,也就是说只有x和y异号的时候才可能出现溢出。而若是没有溢出的可能,则x-y的最高位便必然是所求的结果。那么就来模拟一个if判断即可以了。这里将flag=x^y做为判断项,若是flag=1说明可能会溢出,那么若是是负数-正数返回1,若是是正数-负数返回0:若是flag=0则不会产生溢出了,就用x-y来判断。

/* 可能会形成溢出的状况:
负数-正数 1
正数-负数 0
(异号)

必定不会形成溢出:
正数-正数
负数-负数
(同号)
x-y
*/

int isLess(int x, int y) { int t = x+~y+1;///x-y int flag = x^y; return (((flag&x)|(t&~flag))>>31)&1; }

 

13.

/* * satAdd - adds two numbers but when positive overflow occurs, returns * maximum possible value, and when negative overflow occurs, * it returns minimum positive value. * Examples: satAdd(0x40000000,0x40000000) = 0x7fffffff * satAdd(0x80000000,0xffffffff) = 0x80000000 * Legal ops: ! ~ & ^ | + << >> * Max ops: 30 * Rating: 4 */

 

解题思路:题意要求若是两个数相加,若是产生了正溢出返回0x7fffffff,出现负溢出返回0x80000000,其余状况返回两数之和。

这里个人思路很简单就是使用位运算来模拟一下if判断,有点数据选择器的意思?开始就先构造0x80000000和0x7fffffff这两数数刚好构造出一个另外一个取反就能够了。产生溢出只有两种状况正数加正数溢出产生负数,负数加负数溢出产生正数,因此根据这一特色就能够判断是否产生溢出,在根据两数相加获得的数的正负就能够继续判断正溢出和负溢出了。

 

/*
产生溢出:
1.正数+正数=负数 正溢出 0x7fffffff
2.负数+负数=正数 负溢出 0x80000000

不溢出:
x+y

*/

int satAdd(int x, int y) 
{ 
    int ans = x+y;
    int over = (x^ans)&(y^ans);
    int min_ans = 1<<31;///0x80000000
    int max_ans = ~(min_ans);//0x7fffffff
    int neg_over = ((over&x)>>31)&1;
    int pos_over = ((over&~x)>>31)&1; 
    return ((~pos_over+1)&max_ans)+((~neg_over+1)&min_ans)+((~(!(neg_over|pos_over))+1)&ans); 
}

 

14.

/* 
 * float_twice - Return bit-level equivalent of expression 2*f for
 *   floating point argument f.
 *   Both the argument and result are passed as unsigned int's, but
 *   they are to be interpreted as the bit-level representation of
 *   single-precision floating point values.
 *   When argument is NaN, return argument
 *   Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
 *   Max ops: 30
 *   Rating: 4
 */

 

先插入一个怎么把十进制小数转换成二进制的小数的方法---->

 

解题思路:将一个浮点数扩大两倍。首先若是参数是非数NaN就返回参数自己,那么就先将这一种状况排除掉。对于大多数浮点数阶码不为0,那么阶码加1即可以加倍。对于那些阶码为0的,如果尾数不为0,尾数左移1位即可以实现加倍了;若也尾数为0,那么这个数就是0了,返回原来的0便可。

开始我在这里有一个疑惑,尾数左移一位要是进入了阶码的范围,还对吗?其实这也是正确的,由于如果尾数可以进位,那么阶码加1,整个浮点数值确实是加倍了。

同时这里还要说明一下,尾数是用23位数来表示24位尾数,基数是2,须要规格化尾数形式为+-0.1bb...b,其中第一位“1”不明显表示出来。

举个例子:

-5.625 = C 0 B 4 0 0 0 0

C 0 B 4 0 0 0 0 1100 0000 1011 0100 0000 0000 0000 0000 按照浮点数格式切割成相应的域 1 1000 0001 01101 000000000000000000 经分析:符号域1 意味着负数;指数域为129 意味着实际的指数为2 (减去误差值127); 尾数域为01101 意味着实际的二进制尾数为1.01101 (加上隐含的小数点前面的1)。

因此,实际的实数为:

= -1.01101 × 2^2

=- ( 1*2^0 + 1*2^(-2) + 1*2^(-3) + 1*2^(-5) ) × 2^2

= -(1+0.25+0.125+0.03125)*4

= -1.40625*4

= -5.625

 

unsigned float_twice(unsigned uf)
{
    unsigned S = uf&0x80000000;///得到第0位的符号位
    unsigned Exp = uf&0x7f800000;///得到第1~8位的阶码
    unsigned M = uf&0x007fffff;///得到第9~31的尾数
    unsigned T = uf&0x7fffffff;
    unsigned ans = uf;
    if(T >= 0x7f800000)///若是是NaN的状况
    {
        return uf;
    }
    if(Exp!=0)///阶码不为0,那想要翻倍就只须要阶码加1便可。
    {
        ans =  uf + 0x00800000;
        // return S+Exp+0x00800000+M;
    }
    else///阶码为0,那就得经过变换尾数来加倍
    {
        if(M!=0)///尾数左移一位加倍
        {
            ans = (uf<<1) + S;
            // return S+E +(M<<1);
        }
    }
    return ans;
}

/*
float中
第0位为符号位
第1~8位为阶码
第9~31位为24位二进制原码小数表示的尾数
举个例子:
(65878)10进制
= (1 0000 0001 0000 0110)2进制
= (0.1000 0000 1000 0011 0)2进制*2^17
符号S =0
阶码E =(128+17)10进制
= (145)10进制
= (1001 0001)二进制
因此浮点型表示为
0 1001 0001 000 0000 1000 0011 0000 0000

*/

相关文章
相关标签/搜索