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

*/

相關文章
相關標籤/搜索