# 2022q1 Homework1 (quiz2) ###### tags: `Linux 核心設計` `成大課程` contributed by < `hbr890627` > [題目連結](https://hackmd.io/@sysprog/linux2022-quiz2) ## 測驗1 想對二個無號整數取平均值,用以下程式可以避免 overflow ,正常執行。 ```c #include <stdint.h> uint32_t average(uint32_t low, uint32_t high) { return low + (high - low) / 2; } ``` 可改寫為以下等價的實作: ```c #include <stdint.h> uint32_t average(uint32_t a, uint32_t b) { return (a >> 1) + (b >> 1) + (EXP1); } ``` 其中: `a >> 1` ,向右 shift 一個位元,等價於 `a/=2` 。 `b >> 1` ,向右 shift 一個位元,等價於 `b/=2` 。 但若是在 shift 之後才將兩者相加,可能會漏掉最右邊的位元,如: `01 + 01 = 10` ,需要在當 `a`, `b` 都為奇數時,補上 1 , 因此 ==EXP1 = a & b & 1==。 :::info 最後要再 `& 1` ,是因為只需要取最右邊位元的資訊就好。 ::: 可以再次改寫為以下等價的實作: ```c uint32_t average(uint32_t a, uint32_t b) { return (EXP2) + ((EXP3) >> 1); } ``` 參考[第 1,2 週課堂問答簡記](https://hackmd.io/5zdyXn6uQMOeSoVBapuVNw?view)中, `Eddielin0926` 的解釋,可以將其思考成全加器除二,原本是 `a + b >> 1` ,將 carry 提出來後就變成 `(a & b) + ((a ^ b) >> 1)`。 ==EXP2 = a & b== ==EXP3 = a ^ b== ## 測驗2 在〈[解讀計算機編碼](https://hackmd.io/@sysprog/binary-representation)〉一文中的「不需要分支的設計」一節提供的程式碼 `min`: ```c int32_t min(int32_t a, int32_t b) { int32_t diff = (a - b); return b + (diff & (diff >> 31)); } ``` `diff >> 31` ,取最左邊的位元,看是正數或負數。 如果是正數, `diff & (diff >> 31)` 等於零,會 return `b`。 如果是負數, `diff & (diff >> 31)` 等於 `diff` ,會 return `b` + `diff` = `a`。 將上面程式碼進行改寫,可以得到以下實作 (`max`): ```c #include <stdint.h> uint32_t max(uint32_t a, uint32_t b) { return a ^ ((EXP4) & -(EXP5)); } ``` 考慮兩種情況: 1. 當 `a >= b` 時,根據 XOR 的特性,`((EXP4) & -(EXP5))` 要等於 0 時, 才會 return `a`,因此 `-(EXP5)` 要等於 0 才有機會, ==EXP5 = a < b== 。 2. 當 `a < b` 時,根據 XOR 的特性,`((EXP4) & -(EXP5))` 內應該要有包含 `a ^ b` 的部分,才能再 `a ^ (a ^ b)` 後 return `b`,==EXP4 = a ^ b== 。 ## 測驗3 以下為 64 位元 GCD (greatest common divisor, 最大公因數) 求值函式: ```c #include <stdint.h> uint64_t gcd64(uint64_t u, uint64_t v) { if (!u || !v) return u | v; while (v) { uint64_t t = v; v = u % v; u = t; } return u; } ``` 1. 當 u, y 都是 0 時,return 0。 2. 當 u=0 時,return v,當 v=0 時,return u。 3. 使用輾轉相除法找到最大公因數。 可改寫為: ```c #include <stdint.h> uint64_t gcd64(uint64_t u, uint64_t v) { if (!u || !v) return u | v; int shift; for (shift = 0; !((u | v) & 1); shift++) { u /= 2, v /= 2; } while (!(u & 1)) u /= 2; do { while (!(v & 1)) v /= 2; if (u < v) { v -= u; } else { uint64_t t = u - v; u = v; v = t; } } while (COND); return RET; } ``` ```c if (!u || !v) return u | v; int shift; for (shift = 0; !((u | v) & 1); shift++) { u /= 2, v /= 2; } ``` 1. 當 u, y 都是 0 時,return 0。 2. 當 u=0 時,return v,當 v=0 時,return u。 3. 判斷 `u`, `v` 是否都為偶數,並紀錄 shift 的次數。 ```c while (!(u & 1)) u /= 2; ``` 4. 將 `u` 其中二的因數提出來。 ```c do { while (!(v & 1)) v /= 2; if (u < v) { v -= u; } else { uint64_t t = u - v; u = v; v = t; } } while (COND); ``` 5. 根據輾轉相除法,兩數要不斷相減直到 `v` 變為 0 為止,因此 `COND = v!=0`,將其簡化符合Linux的風格,==COND = v==。 ```c return RET; ``` 6. 最終,要 `return` 剩下不為 `0` 的數字,==RET = u << shift==。 ## 測驗4 在影像處理中,bit array (也稱 bitset) 廣泛使用,考慮以下程式碼: ```c #include <stddef.h> size_t naive(uint64_t *bitmap, size_t bitmapsize, uint32_t *out) { size_t pos = 0; for (size_t k = 0; k < bitmapsize; ++k) { uint64_t bitset = bitmap[k]; size_t p = k * 64; for (int i = 0; i < 64; i++) { if ((bitset >> i) & 0x1) out[pos++] = p + i; } } return pos; } ``` ## 測驗5 ## 測驗6 ## 測驗7 ## 測驗8