標籤:style blog http color io 使用 ar strong for
第九題
#include <stdio.h>int main(){ float f=0.0f; int i; for(i=0;i<10;i++) f = f + 0.1f; if(f == 1.0f) printf("f is 1.0 \n"); else printf("f is NOT 1.0\n"); return 0;}
知識點講解:
浮點寄存器是FPU的組成部分。硬體架構不同,浮點寄存器的個數和位元也不同。X86架構的浮點寄存器有:
1)8個80位的資料寄存器:FPR0~FPR7,資料寄存器的位元決定著電腦的計算精度,位元越多,計算精度越高;
2)3個16位寄存器:1個標記寄存器,1個控制寄存器,1個狀態寄存器;
3)2個48位寄存器:1個指令指標,1個資料指標。
FPU對浮點寄存器的操作有自己的一套指令,對於資料寄存器,不能直接使用這8個寄存器的名字,這8個資料寄存器被設計成首尾相連的堆棧,st(0)指向FPRx的棧頂。
參考文章:
http://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html
http://floating-point-gui.de/
http://en.wikipedia.org/wiki/IEEE_754
http://en.wikipedia.org/wiki/Floating_point
根據國際標準IEEE 754,任意一個二進位浮點數V可以表示成下面的形式:
V = (-1)S × M × 2E
關於S,M,E所佔位元如下表所示:
Type |
Total bits |
S |
E |
M |
Exponent bias |
Bits precision |
Single |
32 |
1 |
8 |
23 |
127 |
24 |
Double |
64 |
1 |
11 |
52 |
1023 |
53 |
Double extended |
80 |
1 |
15 |
64 |
16383 |
64 |
M:1≤M≤2,即M總是可以表示成1.xxx的形式,由於M第一位總是1,在寄存器內表示M時,第一位的1被捨去,只儲存小數部分,所以節省了1位有效數字。以32位浮點數為例,M佔23位,但有效數位位元為24。
E:存放E時,需要將E加上一個固定值,對於32位浮點數,固定值為127,對於64位浮點數,固定值為1023。更多關於E的講解見第一個參考連結。
舉例:
float x = 0.15625;
0.15625用二進位表示為0.00101
S=0, E=-3, M=1.01
寄存器中存放E的部分應存放-3+127;
寄存器中存放M的部分應存放01
故0.15625在寄存器中的表示為:
0 01111100 01000000000000000000000
題目講解:
浮點數在電腦中並不能精確表示,表示值與實際值有微小的誤差,這種誤差會隨著加減法疊加。比較浮點數不可簡單地用“>”“<”“==”“!=”來判斷。
浮點數的比較方法可以參考
http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
第十題
#include <stdio.h>int main(){ int a = 1,2; printf("a : %d\n",a); return 0;}
知識點講解:
逗號運算式的形式如下:
(運算式1,運算式2,運算式3,……,運算式n)
(1)逗號運算式的計算過程為從左往右逐個計算;
(2)逗號運算式作為一個整體,它的值為最後一個運算式的值;
(3)逗號運算子的優先順序在所有運算子中最低。
題目講解
由於逗號運算子的優先順序最低,所以
int a = 1,2;
等同於
int (a = 1),2
故編譯有誤。
應改為:
int a = (1,2);
第十一題
#include <stdio.h>int main(){ int i=43; printf("%d\n",printf("%d",printf("%d",i))); return 0;}
題目講解:
printf的傳回值為列印的字元數,man 3 printf中的描述如下:
Upon successful return, these functions return the number of characters printed (not including the trailing “\0”used to end output to strings).
故這段程式的輸出為:4321
第十二題
void duff(register char *to, register char *from, register int count) { register int n=(count+7)/8; switch(count%8){ case 0: do{ *to++ = *from++; case 7: *to++ = *from++; case 6: *to++ = *from++; case 5: *to++ = *from++; case 4: *to++ = *from++; case 3: *to++ = *from++; case 2: *to++ = *from++; case 1: *to++ = *from++; }while( --n >0); } }
知識點講解
通知編譯器將register變數“儘可能”地放入寄存器當中,以提高對該變數的存取速度。普通變數放在記憶體中,其存取速度跟記憶體速度相當,register變數存放於寄存器當中,其存取速度和cpu速度相當。對於需要頻繁逐一查看的變數定義成register類型可提高程式運行效率。
a++:取a的地址,把它的值裝入寄存器,然後增加記憶體中a的值;
++a:取a的地址,增加記憶體中a的值,把a的值裝入寄存器。
關於C語言運算優先順序參考:
http://www.slyar.com/blog/c-operator-priority.html
對於*to++,*和++屬於同級運算子,結合方向為從右至左,故*to++ == *(to++),操作步驟有三:
1、取to的值到寄存器;
2、將to的值加1;
3、取步驟一的寄存器中的地址指向的值。
“*to++ = *from++;”的含義為從源地址處複製一個位元組到目的地址處,並將原地址和目的地址分別向後移一個位元組。
關於達夫裝置參考:
http://www.drdobbs.com/a-reusable-duff-device/184406208
達夫裝置是串列複製的一種最佳化實現,通過減少迴圈次數來提高程式的執行效率。
當要把一段資料從源地址複製到目的地址時,你會用什麼樣的方法?
境界一:
void my_copy_1(register char *to, register char *from, register int count){ while(count-- > 0) { *to++ = *from++; }}
點評:這段代碼簡潔易懂,但執行效率卻不高。每複製完一個位元組後都要執行三個附加動作:1.跳轉到迴圈開始處;2.改變count的值;3.將count值和0比較。這三個附加動作消耗的時間遠遠大於複製動作消耗的時間,代碼有點頭重腳輕了。
想一想如果你是cpu,每複製一個位元組都讓你檢測一次是否超過count值,你一定覺得你的主人好煩。如果能讓cpu順溜地,沒有阻礙地一直複製下去就好了……
境界二:
void my_copy_2(register char *to, register char *from, register int count){ register int n_block = count/8; register int n_left = count%8; while (n_block-- > 0) { *to++ = *from++; *to++ = *from++; *to++ = *from++; *to++ = *from++; *to++ = *from++; *to++ = *from++; *to++ = *from++; *to++ = *from++; } while (n_left-- > 0) { *to++ = *from; }}
點評:複製大段資料時,讓cpu獃頭獃腦地一直copy下去那是最快的,但是我們總得檢查已經複製的資料量是否超過了設定值。一步一回頭顯然沒有必要,N歩一回頭還是可以考慮的。上段程式中把N設為了8,當然也可以根據實際需要設成其他的值。
上面程式中對“零碎資料”的處理仍是採用“一步一回頭”的老方法,對於這段資料的處理,有沒有更簡潔的方法呢?
境界三:
void my_copy_3(register char *to, register char *from, register int count){ register int n_block = (count+7)/8; int n_left = count%8; switch (n_left) { case 0: do{*to++ = *from++; case 7: *to++ = *from++; case 6: *to++ = *from++; case 5: *to++ = *from++; case 4: *to++ = *from++; case 3: *to++ = *from++; case 2: *to++ = *from++; case 1: *to++ = *from++; } while(--n_block > 0); }}
點評:上述代碼先處理零碎的資料,再處理塊狀的資料。利用switch語句,根據零碎資料量跳轉到相應的case標號處,如果零碎資料量為7,那就一路往下執行7次賦值動作,為6就一路執行6次賦值動作,以此類推……這種方法執行比較操作的次數最少。上面這段串列複製的方法就叫做“達夫裝置”。
當然我們也可以用宏來實現達夫裝置,宏定義如下:
#define DUFF_DEVICE_8(aCount, aAction) do { int count_ = (aCount); int times_ = (count_ + 7) >> 3; switch (count_ & 7) { case 0: do{aAction; case 7: aAction; case 6: aAction; case 5: aAction; case 4: aAction; case 3: aAction; case 2: aAction; case 1: aAction; } while(--times_ > 0); } }while (0)
點評:num除以8和num對8取餘有兩種方法:一種是num/8,num%8;一種是num>>3,num&7。後一種方法高效一點。
C puzzles詳解【9-12題】