深入理解計算機系統——datalab實驗

實驗目的:

完善bits.c裏的各個函數,實現其功能,並通過btest的測試

實驗說明:

實驗的目標是修改bits.c的副本,以便它通過所有在btest中進行測試而不違反任何編碼準則。

1、使用dlc編譯器(./dlc)自動檢查代碼是否符合標準。

命令:unix> ./dlc bits.c
說明:如果代碼沒有問題,dlc會直接返回,否則,它會打印標記問題的消息。

命令:unix> ./dlc -e bits.c
說明:dlc打印每個功能使用的操作員數量。

2、使用btest進行測試

命令:unix> make btest
unix> ./btest [可選命令行參數]
說明:編譯和運行btest程序(每次更改bits.c都要執行make btest重新編譯)。

命令:unix> ./btest
說明:測試所有功能的正確性並打印出錯誤信息。

命令:unix> ./btest -g
說明:以緊湊的形式測試所有功能、錯誤消息。

命令:unix> ./btest -f foo
說明:測試函數foo的正確性。

命令:unix> ./btest -f foo -1 27 -2 0xf
說明:用特定參數測試函數foo是否正確。

3.助手程序

ishow和fshow程序可以查看整數和浮點表示,都需要單個十進制或十六進制數作爲參數。要構建它們,執行命令:unix> make

示例用法:
unix> ./ishow 0x27
十六進制= 0x00000027,有符號= 39,無符號= 39

unix> ./ishow 27
十六進制= 0x0000001b,有符號= 27,無符號= 27

unix> ./fshow 0x15213243
浮點值3.255334057e-26
位表示形式0x15213243,符號= 0,指數= 0x2a,分數= 0x213243
標準化:+1.2593463659 X 2 ^( - 85)

linux> ./fshow 15213243
浮點值2.131829405e-38
位表示形式0x00e822bb,符號= 0,指數= 0x01,分數= 0x6822bb
標準化: +1.8135598898 X 2 ^( - 126)

實驗內容及操作步驟:

完善bits.c中的函數並按照要求實現其功能,利用編譯器進行測試。

一. bitAnd

題目:只能用~和|來實現位的與操作。

具體要求:
/*

  • bitAnd - x&y using only ~ and |
  • Example: bitAnd(6, 5) = 4
  • Legal ops: ~ |
  • Max ops: 8
  • Rating: 1
    */

思路:這道題沒什麼難度,根據學習的離散數學知識,很簡單可以通過摩根定律得到AB=!(!A+!B)

代碼如下:
int bitAnd(int x, int y) {
  return ~(~x|~y);                   //德摩根律,得出結果
}

二. getByte

題目:給定n (0<=n<=3),求出第n個字節是哪數字。

具體要求:
/*

  • getByte - Extract byte n from word x
  • Bytes numbered from 0 (LSB) to 3 (MSB)
  • Examples: getByte(0x12345678,1) = 0x56
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 6
  • Rating: 2
    */

思路:要想知道某個字節是哪個數字,必須將這個字節保留在最低位字節。然後每個字節是8位因此字節數n左移3(左移x位擴大2x倍)便是需要移動的位數。再與0xff進行與運算,清除高三位字節的信息並保留最低位字節的信息。

代碼如下:
int getByte(int x, int n) {
int temp=x>>(n<<3);                 //將x左移相應字節數
return temp&0xff;                   //進行與運算得到最終結果
}

三. logicalShift

題目:將x按邏輯右移移動n(0<=n<=31) 位。

具體要求:

/*

  • logicalShift - shift x to the right by n, using a logical shift
  • Can assume that 0 <= n <= 31
  • Examples: logicalShift(0x87654321,4) = 0x08765432
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 20
  • Rating: 3
    */

思路:首先知道計算機的>>符號實際上是算術右移,左邊會補上符號位,那麼想法就是將補上的符號位都改爲0.

第一種想法:

將補上的符號位與0相與,將其餘各位與1相與,得到邏輯右移的結果。
利用算術右移、左移,先構造高位爲1,低位爲0的數:先將1左移31位,再右移n-1位,即先右移n位再左移1位,取反得到需要的結果,然後將算術右移的結果與上述結果進行與運算,得到邏輯右移的結果。

代碼如下:
int logicalShift(int x, int n) {
  int s = 1<<31;                  //將1左移31位             
  int temp=~((s>>n)<<1);          //將s右移n-1位,並取反
  return (x>>n)&temp;             //temp與x算術右移n位結果相與
}
第二種想法:

是通過全1數向左移32-n位得到(其中注意爲了避免出現移32位,採用了<<32+~n<<1),進行取反,然後將算術右移的結果與上述結果進行與運算,得到邏輯右移的結果。
說明:對於右移大於或等於位寬的操作,或者右移負數的操作,其結果將依賴於編譯器的處理和硬件指令的處理,結果並不唯一。

代碼如下:
int logicalShift(int x, int n) {
 int temp=~0<<(32+(~n))<<1;  //全爲1先左移32-n-1位,再左移一位
 return (x>>n)&(~temp);              //將算術右移的結果與上述結果取反進行
與運算,得到邏輯右移結果。
}

四. bitCount

題目:用位運算計算出x中有多少個1

具體要求:

/*

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

思路:因爲要求比較嚴格,所以採用常規的方法會超出範圍,在此想到兩種思路。

第一種想法:

分治的思想

在這裏插入圖片描述

以下爲每次檢測4位的情況: 初始化tmp=0x1111,用來以此檢測x>>i的0,8,16,24位是否爲1; 利用val累加分別計算4個字節上1的個數,val的每個字節的值爲對應x每個 字節上的1的個數;
最後將得到val四個字節的值相加,即x四個字節上1的個數的和,保留最低 字節的信息爲最後結果

代碼如下:
int bitCount(int x) {
	int sum=0,mask;
	mask=0x11|(0x11<<8);            //mask = 0x1111
	mask=mask|(mask<<16);           //mask = 0x11111111
	sum+=x&mask;                    //檢測每四位中的sum的個數,分別也位於
sum的不同的8個部分中
	sum+=(x>>1)&mask;
	sum+=(x>>2)&mask;
	sum+=(x>>3)&mask;
	                                //4 位一組
	mask=0xff|(0xff<<8);            //mask = 0xffff
	sum=(sum&mask)+((sum>>16)&mask);//現在sum的低16位已經比較成功地保存
了值,而且不會產生進位
	mask=(0xf<<8)+0xf;              //mask = 0x0f0f
	 sum=(sum&mask)+((sum>>4)&mask); //現在0x0f0f對應的隔兩個4位也有了對應
值,爲什麼用0x0f0f呢,想一下,這是極端
情況時低4*4位均爲1000,如果用0x00ff
必然會導致進位的混亂,於是使用0f0f保存
進位。
	mask=0xff;
	sum=(sum&mask)+((sum>>8)&mask);//現在結果就位於了最低幾位,保存了進位,
中間加了一個0f0f使得不至於沖掉進位
使結果錯誤。
	return sum;
}
第二種想法:

將32位劃分爲4個部分,每次檢測8位,將檢測的結果累加。

代碼如下:
int bitCount(int x) {
  int temp=(((0x1<<8|0x1)<<8|0x1)<<8|0x1)<<8|0x1;
  int val=temp&x;
  val+=temp&(x>>1);
val+=temp&(x>>2);
val+=temp&(x>>3);
val+=temp&(x>>4);
val+=temp&(x>>5);
val+=temp&(x>>6);
val+=temp&(x>>7);                       //8位一組
val+=(val>>16);
val+=(val>>8);
return val&0xff;
}

五. bang

題目:不能用!運算符求出!x結果

具體要求:

/*

  • bang - Compute !x without using !
  • Examples: bang(3) = 0, bang(0) = 1
  • Legal ops: ~ & ^ | + << >>
  • Max ops: 12
  • Rating: 4
    */

思路:本題比較簡單,只需要判斷是不是0即可,然而0的特殊性,0的相反數仍然是0。

第一種想法:

考慮到只有0這個數它所有的位都爲0,也就是說它的反碼全爲1,那麼用二分法逐漸將所有的位相與最終必定爲1;而其他必然會在每個過程中,因爲某個位上的值爲0而導致二分法求解時最終值爲0。

代碼如下:
int bang(int x) {
	int t=~x;
	t=t&(t>>16);
	t=t&(t>>8);
	t=t&(t>>4);
	t=t&(t>>2);
	t=t&(t>>1);                       //二分法依次進行與運算
	t&=0x1;                           
	return t;
}
第二種想法:

利用補碼即其相反數,0的相反數符號位爲0,而其他所有數的符號位都一定與它相反數的符號位相反,所以相或時,必定值爲1,可以將其求反得0。

代碼如下:
int bang(int x) {
  int t=(~x)+1;                       //求相反數
  int flag=~((x|t)>>31)&0x01;         //相反數和自身相或保留符號位
  return flag;
}

六. tmin

題目:返回補碼整數的最小整數數值。

具體要求:

/*

  • tmin - return minimum two’s complement integer
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 4
  • Rating: 1
    */

思路:此題無難度。

拓展:計算機中的符號數有三種表示方法,即原碼、反碼和補碼。 三種表示方法均有符號位和數值位兩部分, 符號位都是用0表示“正”,
用1表示“負”,而數值位,三種表示方法各不相同。 在計算機系統中,數值一律用補碼來表示和存儲。
原因在於,使用補碼,可以將符號位和數值域統一處理;同時,加法和減 法也可以統一處理。
此外,補碼與原碼相互轉換,其運算過程是相同的,不需要額外的硬件電 路。在補碼中約定成俗,100 …000表示-2^(n-1),爲最小值

代碼如下:
int tmin(void) {
  int ans=(1<<31);
  return ans;
}

七. fitsBits

題目:只給出n個二進制位,能否表示x。

具體要求:

/*

  • fitsBits - return 1 if x can be represented as an
  • n-bit, two’s complement integer.
  • 1 <= n <= 32
  • Examples: fitsBits(5,3) = 0, fitsBits(-4,3) = 1
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 15
  • Rating: 2
    */

思路:如果是一個正數或零的話,且能用相應的補碼錶示,則向右移動n-1位之後,此時該數的數值一定爲0,如果該數是一個負數的話,如果移動n-1位後,該數的數值應該一定等於爲-1,所以讓x右移n-1位然後判斷是否全是0或者全是1便可以。

代碼如下:
  int fitsBits(int x, int n) {
  int temp = n+(~0);        //n+0xffffffff指n-1,全1在機器中表示-1。
   int tempx = x >> tmp;         //x右移n-1位
   int ans = (!tempx|!(tempx+1));//判斷是否全爲0或全爲1
   return ans;
}

八. divpwr2

題目:給出整數x,整數n,求[x/(2^n)],答案要接近趨向0方向。

具體要求:

/*

  • divpwr2 - Compute x/(2^n), for 0 <= n <= 30
  • Round toward zero
  • Examples: divpwr2(15,1) = 7, divpwr2(-33,4) = -2
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 15
  • Rating: 2
    */

思路:計算機中,兩個int型的a和b,a/b向下取整,比如17/16,區間爲(1,2),向
下取整爲1,而對於負數如果-17/16區間爲(-2,-1),向下取整則出現錯誤。

第一種想法:

正數直接右移n位則可以,負數則需要判斷一下,讓負數向上取整,加一個偏置值。偏置值爲2n-1.

代碼:
int divpwr2(int x, int n) {
    int temp = (~( (x >> 31) & 0x1) )+1;//提取符號位並擴展
    int q= (1<<n)+(~0);           //構造偏置值
    int ans = (x + (temp & q) ) >> n ;  //修正後,移位實現出發
    return ans;
}
第二種想法:

先獲取符號位,利用符號位進行修正;然後將x的低n位保存下來,如果爲負數且低n位不爲0則給結果加1

代碼如下:
int divpwr2(int x, int n) {
    int s=!!(x>>31);            //獲取符號位 
    int t=(1<<n)+(~0);            
    int lowx=t&x;               //上一步得到結果與x進行與運算得到x低n位
    return (x>>n)+((!!lowx)&s); //判斷n位是否爲0,並根據符號位修正
}

九. negate

題目:給定x求-x。

具體要求:

/*

  • negate - return -x
  • Example: negate(1) = -1.
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 5
  • Rating: 2
    */

思路:沒什麼難度,按位取反+1,直接返回負數。

代碼如下:
int negate(int x) {
  int ans=(~x)+1;                      //按位取反並加一
  return ans;
}

十. isPositive

題目:判斷x是不是正數

具體要求:

/*

  • isPositive - return 1 if x > 0, return 0 otherwise
  • Example: isPositive(-1) = 0.
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 8
  • Rating: 3
    */

思路:反向思維,去判斷不是正數的情況,負數的話符號位爲1,0的話全部爲0.

第一種想法:

將這兩種情況進行或運算,然後再取反即最終答案。

代碼如下:
int isPositive(int x) {
    int temp = (x>>31) & 0x1;         //取符號位
    int ans = !(  temp |  !x );       //判斷是否爲負數或0
      return ans;
}
第二種想法:

x的無符號值必定或者爲0,或者不爲0,這裏只可能是爲負數(!flag=0,!x=0),或者爲0(!flag=1,!x=1),爲正數時(!flag=1,!x=0),所以可以採用異或。

代碼如下:
int isPositive(int x) {
	int flag=(x>>31);
	flag&=0x1;                       //取符號位
	return (!flag)^(!x);             //使用異或進行判斷
}

十一. isLessOrEqual

題目:用位運算判定x<=y,如果是就返回1,如果不是就返回0。

具體要求:

/*

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

思路:想判斷x<=y,肯定要去判斷y-x是否位負數,但是又考慮到最大的整數減去最小的負數會溢出導致出現錯誤的結果,那麼想辦法解決這個問題。

  • 當相同符號時,直接相減判斷是否爲負數即可。
  • 當前者爲正,後者爲負時,直接返回0,前者爲負後者爲正時返回1。
代碼如下:
int isLessOrEqual(int x, int y) {
   int signx = (x >> 31) &0x1;       //取x的符號位
   int signy = (y >> 31) &0x1;       //取y的符號位
   int isSameSign=! (signx ^ signy) ;// 對符號位異或取反,判斷是否相同
   int p=! ( ( (~x)+1+y ) >> 31);    //計算y-x,並取結果的符號位
                                     //p爲1表示x<=y(x,y同號)
   return ( isSameSign & p ) | ( ( !isSameSign ) & signx));
}

十二. ilog2

題目:求整數的log(x)。

具體要求:

/*

  • ilog2 - return floor(log base 2 of x), where x > 0
  • Example: ilog2(16) = 4
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 90
  • Rating: 4
    */

思路:該題和之前那道求1的個數類似,也需要採用二分的思想,先判斷第一個1在前16位還是後16位,記錄下來,再判斷再前8位還是後8位,一直做到判斷在前1位還是後1位,便是最終答案。公式log(x)=16a+8b+4c+2d+e。那麼count=abcde。因爲x長32位,首先我們先將x>>16,判斷高16位是不是還>0,如果>0,!(x>>16)就是0,我們要將他轉換到a的位置就是將!!(x>>16)再次取非是1,然後<<4,到a的位置,就說明這個數大於16,1肯定在高16位處,然後在接着將高位折半到8位,就是>>8+16,看看高8位是不是也是>0。依次進行下去直到判斷前一位還是後一位……(此處應該爲無符號數)

代碼如下:
int ilog2(int x) {
   int count = 0;
   count = (!!(x>>16)) << 4;              //判斷前十六位有沒有值,決定起點
   count =count+((!!(x>>(8 + count)))<<3);//判斷剩下十六位的前八位
   count =count+((!!(x>>(4 + count)))<<2);//判斷剩下八位的前四位
   count =count+((!!(x>>(2 + count)))<<1); //判斷剩下四位的前兩位
   count =count+((!!(x>>(1 + count)))<<0); //判斷剩下兩位的前一位
   return count;
}

十三. float_neg

題目:就是返回輸入uf的負數形式-uf。如果uf是NAN形式就直接返回參數。

具體要求:

/*

  • float_neg - Return bit-level equivalent of expression -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 representations of
  • single-precision floating point values.
  • When argument is NaN, return argument.
  • Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
  • Max ops: 10
  • Rating: 2
    */

思路:浮點數是由s(符號1位)E(階碼8位)M(尾數23位)(-1)^s * M * 2^E組成的浮點數,當E是0xFF時,要麼這個數是inf(無窮)或NAN。

  • inf特殊在M就是全0 計算-uf(不考慮函數裏參數和返回值類型,uf解釋爲浮點數),符號位取反可利用異或 (符號位的求反)
  • 當x=NaN時,階碼全爲1,小數位不爲0,返回NaN; (非數值數的特殊情況)
  • 當x=!NaN時,直接將符號位取反返回-x即可;
    (因爲階碼用移碼錶示,不 須求反,尾數也不須求反,因爲並非像負數那樣用補碼錶示)
第一種想法:

先把uf<<1忽略了s,就看看0xFF000000在uf<<1的位置是不是也是
0xFF000000,如果是再判斷一下uf是不是就等於0xFF000000就代表他原先就是個inf數,如果不等於0xFF000000就代表他原先就是個NAN數。然後如果是NAN數的話直接返回原值便可以了。

代碼如下:
unsigned float_neg(unsigned uf) {
   unsigned s=0x80000000;
   unsigned no=0xFF000000;
   unsigned tmp = uf<<1;
   if( (no&tmp ) ==  no)            // 判斷E是否全爲一
   {
        if(tmp != no) return uf;    //判斷是否爲inf,不是返回uf
   }
   return uf^s;
}
第二種想法:

判斷是否,階碼並非全爲1,或着尾數爲0 ,如果是,則直接符號位取反,返回,否則爲NAN,直接返回。

代碼如下:
unsigned float_neg(unsigned uf) {
if((((uf>>23)&0xff)^0xff)||!(uf&((1<<23)-1)))//階碼並非全爲1,或着尾數爲0,通過將階碼按位取反,這裏其實可以用~,當然也可用全1的異或操作
	    uf^=(1<<31);          
//階碼不全爲1或者尾數爲0
//尾數爲0,而階碼不全1,當然需要將其變爲負數
//尾數爲0,而階碼全1,指的是無窮大,也應該需要改變符號位。
//只有當階碼全1且而且尾數不爲0則返回NaN. 除去非數值數就好了。
	return uf; 
}

十四. float_neg

題目:將int型的x轉爲float型的x。

具體要求:

/*

  • float_i2f - Return bit-level equivalent of expression (float) x
  • Result is returned as unsigned int, but
  • it is to be interpreted as the bit-level representation of a
  • single-precision floating point values.
  • Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
  • Max ops: 30
  • Rating: 4
    */

思路:先判斷是否爲0或者-231;然後判斷是否是負數,是負數的話先轉化爲正數,因爲float正負數的區別只是符號位不一樣。再然後將x右移確定階碼的大小。確定了階碼和符號位以後再去考慮尾碼,將x左移到第一個1,然後再右移8,與0x007fffff進行與運算這樣的結果便是除去隱藏位的前23位。讓x與0xff相與,這是23位之後捨去的8位數據,利用浮點數的偶數取餘原則,當大於0.5則進1,當等於0.5且第23位爲1的時候也進1,否則不進位。如果進位後使尾數溢出,那麼讓階碼加1,尾數將溢出位清0.

代碼如下:
unsigned float_i2f(int x) {
	unsigned s=x&(1<<31);           //保存符號位 
	int i=30; 
	int exp=(x>>31)?158:0;          //判斷x是否爲0或-231,對exp進行設置
	int frac=0;                     //尾數初始化爲0,之後進行更新
	int delta;                      //用來保存精度
	int frac_mask=(1<<23)-1;        //frac_mask低23位全1,高9位全0
	if(x<<1)                        //如果x不爲0也不爲-231 (000...000指0,而100...000指-2^31)
	{
	    if(x<0)
	        x=-x;                   //正數變爲負數,因爲要換成原碼實現,由於可以直接這樣轉換,就轉換成爲其原碼,而不用我們之前的表示法
	    while(!((x>>i)&1))          //要看其最高位的位置,一開始向右移30位,而後依次減小,這個30位就是從符號位的下一位起移至最右邊如果有一個時刻這一位變成了1,那麼也就是說,這個i指的就是最高位 
i--;
	    exp=i+127;                  //原碼=E+Bias,Bias=127
		x=x<<(31-i);                 //捨棄前面的0,這時可以看作是尾數,找到第1個1的位置
		frac=frac_mask&(x>>8);       //frac取尾數(取x的高23位),注意這裏前面還有一個1
		x=x&0xff;                    //保留x的低8位 
		delta=x>128||((x==128)&&(frac&1));
                                  //處理精度,四捨五入,判斷是否需要進位,就是我們在上面判斷的,而且需要用到一系列邏輯判斷符進行判斷,遵循向偶數舍入的原則,即||後面的情況
		frac+=delta;
		if(frac>>23)                //如果尾數加上進位溢出 
		{
		    frac&=frac_mask;        //取尾數的後23位
			exp+=1;                  //產生進位 
	    }
	    
}                              //如果爲0或-231則,首先不改變符號位,而後由於我們之前幫階碼賦了值(如果爲負數,階碼值爲31;如果爲0,階碼值爲-127全零),而尾數第一位被省略,所以能產生正確結果。
    return s|(exp<<23)|frac; 
}

十五. float_twice

題目:就是將浮點數乘以2倍。

具體要求:

/*

  • 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
    */

思路: 浮點數的運算要考慮三部分:符號,階碼和尾數計算uf*2,分兩種情況:
(1)階碼部分爲0,只需對尾數部分左移一位;(包含尾數移至階碼位的情況這 裏要注意
(2)階碼部分不爲0,則將階碼加1;(因爲不可以將尾數部分移到階碼部分)

  • 取符號位方法:s=uf&(1<<31);
  • 取8位階碼方法:exp=(uf>>23)&0xff
  • 取尾數:frac=uf&((1<<23)-1); *
  • 判斷uf是否爲NaN,如果是,不做處理,最後返回NaN;

將符號、階碼、尾數三部分整合到返回值;即:ret=s|(exp<<23)|frac

第一種想法:

三個部分進行運算,直接拼接

代碼如下:
unsigned float_twice(unsigned uf) {
    unsigned s=uf&(1<<31);            //取符號位 
	int exp=(uf>>23)&0xff;             //取階碼
	int frac=uf&((1<<23)-1);           //取尾數
	if((exp!=0xff))                    //如果階碼值爲255,且尾數部分不爲0,則該數爲非數值數 
	{
		if(!exp)                        //如果階碼爲0,則將尾數左移一位,在這種情況下如果尾數的最高位爲1,則也可以移到階碼處,從而使得階碼值價一,這樣就進入了規範化數的表示範圍
		{
		    if(frac&0x00400000)
			    exp++;                  //尾數最高位爲1,階碼+1
		    frac = (frac << 1) & 0x007fffff;
                                        //否則,尾數左移
		}
		else                            //如果階碼不爲0 
		{
			exp++;
			if(exp==255)
			    frac=0;                 //如果所得階碼爲255,將尾數設置爲0,表示無窮大 
		} 
	}                                 //省略else情況,即uf爲NaN和無窮大數,在這種情況下,階碼已經全1,無論如何都不能改變了。
	return s|(exp<<23)|frac; 
}
第二種想法:

按位操作。

代碼如下:
unsigned float_twice(unsigned uf) {  
     unsigned f=uf;
    if ((f & 0x7F800000) == 0){ //判斷階碼是否全爲0
                f = ((f & 0x007FFFFF)<<1) | (0x80000000 & f);  
}                           //保留符號位,並將尾數左移,注意分兩種情況:(1)尾數首位爲1,移至階碼處,值確實變爲原來的兩倍(2)尾數首位不爲1,則更加可以放心地移動了
else if ((f & 0x7F800000) != 0x7F800000){ 
                              //判斷階碼是否不全爲1
        f =f+0x00800000;        //階碼+1
        }  
    return f;  
}

相關知識點:

1.常用邏輯運算符
  • a|=b 等效於 a=a|b 按位或並賦值。
  • a&=b 等效於 a=a&b 按位與並賦值。
  • a^=b 等效於 a=a^b 按位異或並賦值。
  • a!=b 邏輯判斷,不等於,當ab不等時爲真。
  • && 邏輯與,均爲真時結果爲真 || 邏輯或,均爲假時結果爲假。
  • !a 邏輯非,單目,a爲真時結果爲假,否則結果爲真。
  • | 按位或
  • ^ 按位異或
  • & 按位與
  • ~ 按位取反
2.左移32位,根據編譯器不同,結果不同,所以在實際的運算過程中要避免左移32位。

左移就是把一個數的所有位都向左移動若干位,在C中用<<運算符.例如:

int i = 1;
i = i << 2; //把i裏的值左移2位

也就是說,1的2進制是000…0001(這裏1前面0的個數和int的位數有關,32位機器,gcc裏有31個0),左移2位之後變成 000…0100,也就是10進制的4,所以說左移1位相當於乘以2,那麼左移n位就是乘以2的n次方了(有符號數不完全適用,因爲左移有可能導致符號變化,下面解釋原因)

需要注意的一個問題是int類型最左端的符號位和移位移出去的情況.我們知道,int是有符號的整形數,最左端的1位是符號位,即0正1負,那麼移位的時候就會出現溢出,例如:

int i = 0x40000000; //16進制的40000000,爲2進制的01000000…0000
i = i << 1;

那麼,i在左移1位之後就會變成0x80000000,也就是2進制的100000…0000,符號位被置1,其他位全是0,變成了int類型所能表示的最小值,32位的int這個值是-2147483648,溢出.如果再接着把i左移1位會出現什麼情況呢?在C語言中採用了丟棄最高位的處理方法,丟棄了1之後,i的值變成了0.

左移裏一個比較特殊的情況是當左移的位數超過該數值類型的最大位數時,編譯器會用左移的位數去模類型的最大位數,然後按餘數進行移位,如:

int i = 1, j = 0x80000000; //設int爲32位
i = i << 33; // 33 % 32 = 1左移1位,i變成2
j = j << 33; // 33 % 32 = 1 左移1位,j變成0,最高位被丟棄

3.可以利用(1<<n)+(~0)構造低n位全1,再與x進行與運算,可以只保留x的後n位。
4. 浮點數由三部分組成:符號,階碼和尾數。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章