2018q1 Homework2 (assessment) === contributed by <`rwe0214`> ###### tags:`rwe0214` ## 課前測驗題參考作答 ### W1Q2 題目: :::info 給定 B 為 2 的某次方 (power of 2),那麼是否 A % B 會等價於 (A & (B - 1))? (a) 是 (b) 否 ::: ::: success 延伸問題: * 為什麼? * 舉出類似案例並解釋 ::: **想法 & 思考** 從取餘數這個角度出發,一開始我先以十進位來做思考,假設我們要取某數 A 除以 power of 10 的餘數,我們很自然就會把 A 的個位數往前數幾個 B 的次方值。 > e.g. 12345$_d$ 除以 10$^2$ 的餘數就是從十進制的個位開始取兩個數 45$_d$。 回到題目並以二進位的角度來看: > e.g. 123$_d$ = 1111011$_b$ 除以 2$^4$ 的餘數就是從二進制的個位開始取四個數 1011$_b$ = 11$_d$ 所以我們可以確定了一件事,當取 power of 2 的餘數時,擷取被除數之次方個位數即可。接著我們來看 power of 2 的二進制表示 > 2$^3$ = 1000$_b$, 2$^3$ - 1 = 0111$_b$ > 2$^5$ = 100000$_b$, 2$^5$ - 1 = 011111$_b$ 由上面可以看出 (power of 2) - 1 的值剛好在我們想要擷取的位元上都是 1,反之都是 0,如此一來便可以透過 bit mask 的手法將我們的所求位元求出,即題目所述 (A & (B - 1))! 在電腦科學裡,計算 memory 分配進哪一個 cache block 的 index 和 tag 值手法也類似於本題的求餘數。根據 cache 的關聯度 (power of 2) 去分割 memory address bits。 > e.g. 一個關聯度為 4 的 cache,假設一個 set 有 4 個 blocks,且一個 block 是以四個 位元祖為單位 > 某一 memory address 為 m 的資料被放進 cache 時,其 index = m / (set size) / 關聯度,tag = (m / (set size)) % 關聯度。 > 所以 cpu 在計算上也可以直接使用 bit-operator 來求得答案。 ### W2Q2 題目: :::info 完成 float / 2 的程式碼 : ``` clike= typedef unsigned int float_bits; float_bits float_x0_5(float_bits f) { unsigned sig = f >> 31; unsigned rest = f & 0x7FFFFFFF; unsigned exp = f >> 23 & 0xFF; unsigned frac = f & 0x7FFFFF; /* NaN or INF */ if (exp == A0) return f; /* round to even. Last 2 bits of frac are considered: * 00 => 0 just >> 1 * 01 => 0 (round to even) just >> 1 * 10 => 1 just >> 1 * 11 => 1 + 1 (round to even) just >> 1 and plus 1 */ int addition = (frac & 0x3) == 0x3; /* Denormalized */ if (exp == 0) { frac >>= A1; frac += addition; /* Normalized to denormalized */ } else if (exp == 1) { rest >>= A2; rest += addition; exp = rest >> A3 & A4; frac = rest & 0x7FFFFF; /* Normalized */ } else { exp -= A5; } return sig << A6 | exp << 23 | frac; } ``` ::: **想法 & 思考** 首先我把程式碼分成三個部份: 1. 將 IEEE754 的各個 bits ( sign, exp, frac )皆分離出來 2. 判斷傳入的數字是否可以運算 ( NaN or INF ) 3. 若可正確運算則依照數字類型 ( Denormalized, Normalized to denormalized, Normalized ) 去做除法 IEEE754 數字分類: ``` 單精度: | sign | exp | frac ----------|-----------|---------------------|--------------- +NaN | 0 | 1111 1111(FF) | 0 ----------|-----------|---------------------|--------------- +INF | 0 | 1111 1111(FF) | 非零 ----------|-----------|---------------------|--------------- +0 | 0 | 0 | 0 ----------|-----------|---------------------|--------------- Normal | X | 1 ~ FE | X ----------|-----------|---------------------|--------------- Denormal | X | 0 | X ----------|-----------|---------------------|--------------- -0 | 1 | 0 | 0 ----------|-----------|---------------------|--------------- -INF | 1 | 1111 1111(FF) | 非零 ----------|-----------|---------------------|--------------- -NaN | 1 | 1111 1111(FF) | 0 ``` 基於以上規則,A1 ~ A6 的答案也呼之欲出了! 而 A1 ~ A6 之中最讓我感興趣的部份則是 A3 和 A4: ``` clike= /* Normalized to denormalized */ else if (exp == 1) { rest >>= 1; rest += addition; exp = rest >> 23 & 0xFF; frac = rest & 0x7FFFFF; } ``` 如果根據註解來推論這個部份應該是除以 2 之後會是 denormalized 數,而 denormalized 的 exp 值固定為 0,既然這樣為什麼還要大費周章的使用一些運算來求得 exp 呢? 於是我假設有一種情形,一個 normalized 經過以上運算,它確實有可能變成 denormalized,但是最後的結果卻還是 normalized ,先猜測,是這個條件下的最大值。 取二個數其 IEEE754 表示法分別為 `0 00000001 11111111111111111111111` 和 `0 00000001 11111111111111111111110` 並分別紀錄 3,4,5 行並比較其執行結果為 ``` (after rest >>= 1;) rest = 00000000 11111111111111111111111 (after rest += addition;) rest = 00000001 00000000000000000000000 (after exp = rest >> 23 & 0xFF;) exp = 00000001 ``` 和 ``` (after rest >>= 1;) rest = 00000000 11111111111111111111110 (after rest += addition;) rest = 00000000 11111111111111111111110 (after exp = rest >> 23 & 0xFF;) exp = 00000000 ``` 哦!抓到了!原來真的有特例會在最後結果仍為 normalized! 所以為了為了保持其正確性才把這部份使用運算來計算而不是直接 assign 為 0。 ### W2Q3 題目: :::info ```clike= #include <stdio.h> int p(int i, int N) { return (i < N && printf("%d\\n", i) &&!p(i + 1, N)) || printf("%d\\n", i); } int main() { return p(1, 10) && printf("\\n"); } ``` 預期會得到 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 9 -> 8 -> 7 -> 6 -> 5 -> 4 - 填補以下程式碼使得輸出為 1 -> 2 -> 3 -> 4 -> 5 -> 並且包含換行符號 ( \n ) ```clike= #include <stdio.h> int p(int i, int N) { return (i < N && printf("%d -\> ", i) && Q1(i + Q2, N + Q3)); } int main() { return p(1, 10) && printf("\\n"); } ``` ::: **想法 & 思考** 這題當初在測驗的時候因為前面幾題花費較多時間,所以最後沒有時間作答。 不過這題的題目乍看之下很複雜,但是把遞迴展開一些後,便能豁然開朗。 以要填補的程式碼為例,展開幾項後: ``` return p(1, 10) && printf("\\n"); | | V 1<10 && printf("1 ->") && Q1(1+1, 10-1) | | V 2<9 && printf("2 ->") && Q1(2+1, 9-1) | : : ( 過程省略 ) : : | V 5<6 && printf("5 ->") && Q1(5+1, 6-1) | | V 6<5 ,return false ``` 由以上過程可得知若要包含最後的換行符號 ( \n ),則 p(1,10) 必須回傳 true,所以 Q1 為 ~p! ## 因為自動飲料機而延畢的那一年讀後心得