--- tags: linyunwen, raygoah --- # 2018q1 Homework (quiz5-2) ###### tags: `linyunwen` `raygoah` contributed by <`LinYunWen`, `raygoah`> - [2018q1 Homework3 (作業區)](https://hackmd.io/Ex6mTFOvS3K6NizRwX-HVQ) - [第五周練習題 ( 中 )](https://hackmd.io/s/HkQjgqI5G) - [Youtube](https://www.youtube.com/watch?v=zaA9jzWvMlg&list=PLFSyL7YoFilT_HugG5YUnSqslvFrwuIEp) ### Q1 已知在 x86_64 架構,以下程式碼的執行輸出是 `jserv++C`: ```clike #include <stdio.h> int main() { puts((char *) &(double []){ 3823806048287157.0, 96 }); } ``` 考慮以下程式碼,假設 puts 函式總是可正確運作,那麼程式輸出為何? ```clike #include <stdio.h> double m[] = { 3823806048287157.0, 96 }; void gen() { if (m[1]--) { m[0] *= 2; gen(); } else puts((char *) m); } int main() { gen(); return 0; } ``` * 首先先將 3823806048287157.0 以二進位表示,根據 IEEE754 double 的儲存方式如下圖,因此我們先將 3823806048287157 轉成二進位 ![](https://i.imgur.com/aHhNbmf.png) > $(3823806048287157)_{10} = \\ > (11011001\ 01011011\ 10110011\ 10010011\ 00101011\ 10011011\ 0101)_2$ > > 以科學符號表示為 $1.1011001... \times 2^{51}$ > 將小數點後的所有值拿出來從 fraction 的 high bit 開始放 > $^{51}|\ 1011001\ 01011011\ 10110011\ 10010011\ 00101011\ 10011011\ 0101\ 0\ |^0$ (最後面補上一個 0 ) > $(2^{10}-1)$ + **51** > = 1023 + 51 > = $(1074)_{10}$ > = $(1000\ 0110010)_2$ > 放進 exponent 裏 > $^{62}|\ 01000\ 0110010\ |^{52}$ > (前面要補上 0) 因此完成結果為`01000011 00101011 00101011 01110110 01110010 01100101 01110011 01101010` * 因為強制轉型成 char * ,因此接著要將這一長串 0 和 1 轉換成 char ,因此將每 8 個 bits 一組形成一個字元,最後呈現的結果便是 `C++vresj`,但是因為使用的機器其儲存方式為 little-endian,所以顯示的結果會相反:`jserv++C` * 可以看到在 gen() 中,會將 m[0] 的值乘上 2,而因為遞迴的關係,最後 m[0] 會乘上 $2^ {96}$ * 對於乘上 $2^ {96}$ 在 IEEE 754 的規定中,即是要將第 52 到第 62 共 11 個 bits 的 exponent 加上 96,因此會得到 0**1001001 0010**1011 00101011 01110110 01110010 01100101 01110011 01101010,轉換成字元後及為:jserv++I :::success 延伸題目: 1. 修改程式,允許輸出你的 GitHub 帳號名稱 2. 承上,修改 gen() 函式,透過特定的轉換,讓 `m[]` 的內容變為你的英文姓氏 (last name),程式碼應該越短越好,而且不得以字串的形式存在於原始程式碼 3. 如果在 big-endian 的環境中,要達到一樣的效果,我們該如何改寫? ::: :::success 1. 修改程式,允許輸出你的 GitHub 帳號名稱 ```clike= #include <stdio.h> int main() { // output: raygoah puts((char *) &(double []){ 1.08497298767257192667014240249E-306}); // output: LinYunWe puts((char *) &(double []){ 1.5192075502270189308379629358E180}); } ``` - [binary converter](http://www.binaryconvert.com/convert_double.html) 2. 修改 gen() 函式,透過特定的轉換,讓 `m[]` 的內容變為你的英文姓氏 (last name) * 將 raygoah 改成 LEI * raygoah: 0 **00000000110** 1000011000010110111101100111011110010110000101110010 * LEI_____: 0 **10111110101** 1111010111110101111101011111010010010100010101001100 由上面比較可知,sign bit 不變,而 exponent 需要加上 1519,因此乘上 $2^{21519}$, 乘完後,在次方相同的情況下,加上兩者的差距,即可順利轉換成要的結果 * 程式碼如下方所示: ```clike= #include <stdio.h> double m[] = { 1.08497298767257192667014240249E-306, 1519 }; void gen() {&nbsp;&nbsp;&nbsp;&nbsp; if (m[1]--) { m[0] *= 2; gen(); } else { m[0] = m[0] + (0.57218400314947140020383950500E151); puts((char *) m); } } int main() { gen(); return 0; } ``` 3. 在 big-endian 的環境中,要如何改寫 * 使用 [codebeautify](https://codebeautify.org/binary-string-converter) 可以發現到,要讓最後程式輸出得到 raygoah,一開始轉換時要輸入 haogyar,因此在 big_endian 環境中要得到正確結果的話,一開始轉換時就要注意 > 參考[ stackoverflow ](https://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian)。也可以不改變數值,但是另外用一個函式ReverseFloat() 一個一個 char 轉過來。(注意網址中是只有32 bits ,因此只有 4個 char 空間,而若是適用於這題需要 0~7 ,8個空間。) > [name=rex662624] > 在不更改程式碼的前提下,撰寫 Convert "Little-Endian" to "Big-Endian" 的函式,也是可行的做法: > 自己撰寫一個: > [name=workat60474] ```clike= static void Little_to_Big_swap(void *v) { char in[8], out[8]; memcpy(in, v, 8); out[0] = in[7]; out[1] = in[6]; out[2] = in[5]; out[3] = in[4]; out[4] = in[3]; out[5] = in[2]; out[6] = in[1]; out[7] = in[0]; memcpy(v, out, 8); } ``` :::