# 2024q1 Homework4 (quiz3+4) contributed by < [youjiaw](https://github.com/youjiaw/lab0-c) > ## 第 3 週測驗題 ### 測驗 `1` [Digit-by-digit calculation](https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Digit-by-digit_calculation) 提及了一種計算平方根的方式,其原理是將欲求平方根的 $N^2$ 拆成: $$ N^2=(a_{n}+a_{n-1}+a_{n-2}+...+{a_0})^2, a_m=2^m\ or\ a_m=0 $$ 並根據 $(x+y)^{2}=x^{2}+2xy+y^{2}$ 將其擴展為: $$ N^2=a_{n}^2+[2a_{n}+a_{n-1}]a_{n-1}+[2(a_{n}+a_{n-1})+a_{n-2}]a_{n-2}+...+[2(\sum_{i=1}^n a_{i})+a_{0}]a_{0} $$ 以 n=2 舉例,$N^2=(a_{2}+a_{1}+a_{0})^2$ 可以用下方的矩陣表示 $$ \left[ \begin{array}{ccc} a_0a_0 & a_1a_0 & a_2a_0 \\ a_0a_1 & a_1a_1 & a_2a_1 \\ a_0a_2 & a_1a_2 & a_2a_2 \\ \end{array} \right] $$ 將算式調整後可得 $$ N^2=a_{2}^2+[2a_{2}+a_{1}]a_{1}+[2(a_{2}+a_{1})+a_{0}]a_{0} $$ 分別代表以下三個部分 $$ \left[ \begin{array}{ccc} & & \\ & &\vdots \\ & ... & a_2a_2 \\ \end{array} \right] $$ $$ \left[ \begin{array}{ccc} \ddots & \vdots & \vdots \\ ... & a_1a_1 & a_2a_1 \\ ... & a_1a_2 & \ddots \\ \end{array} \right] $$ $$ \left[ \begin{array}{ccc} a_0a_0 & a_1a_0 & a_2a_0 \\ a_0a_1 & \ddots & \vdots \\ a_0a_2 & \vdots & \\ \end{array} \right] $$ 接著令 $P_{m}=\sum_{i=m}^n a_{i}$,則 $P_{0}=a_{n}+a_{n-1}+...+a_{0}$ 就會是我們要求的平方根 N,因此我們需要找出每個 $a_{m}$ 的值。 想檢測 $a_{m}$ 的值是 $2^m$ 還是 0,可以計算 $P_{m}^2$ 與 $N^2$ 的差值,若 $P_{m}^2 \leq N^2$ 就代表 $a_{m}$ 為 $2^m$,否則為0,但是此方法每輪的運算成本太高了。 若是令 * $X_{m}=N^2-P_{m}^2=X_{m+1}-Y_{m}$ * $Y_{m}=P_{m}^2-P_{m+1}^2=(P_{m+1}+a_{m})^2-P_{m+1}^2=2P_{m+1}a_{m}+a_{m}^2$ 就可以透過紀錄 $P_{m+1}$ 來取代 $P_{m}^2$ 的計算。 最後將 $Y_{m}$ 拆為 $c_{m}$、$d_{m}$,可得 * $c_{m}=2P_{m+1}a_{m}$ * $d_{m}=a_{m}^2$ * $Y_m=\left. \begin{cases} c_m+d_m & \text{if } a_m=2^m \\ 0 & \text{if } a_m=0 \end{cases} \right.$ 並藉由位元運算,由 $c_{m}$、$d_{m}$ 推出下一輪的 $c_{m-1}$、$d_{m-1}$。 * $c_{m-1}=P_m2^m=(P_{m+1}+a_m)2^m=P_{m+1}2^m+a_m2^m=\begin{cases} c_m/2+d_m & \text{if }a_m=2^m \\ c_m/2 & \text{if }a_m=0 \end{cases}$ * $d_{m-1}=\dfrac{d_m}{4}$ 最後求得的 $c_{-1}$ 即為所求的 $P_{0}=N$。 程式碼如下: ```c int i_sqrt(int x) { if (x <= 1) /* Assume x is always positive */ return x; int z = 0; for (int m = 1UL << ((31 - __builtin_clz(x)) & ~1UL); m; m >>= 2) { int b = z + m; z >>= 1; if (x >= b) x -= b, z += m; } return z; } ``` 在確認 `x` 的範圍後,會使用三個變數,`z`、`m` 與 `b`,分別對應到先前的 $c_m$、$d_m$ 與 $Y_{m}$。 在每輪迴圈中,無論 $a_{m}$ 值為何,$c_{m}$ 都會除以 2,$d_m$ 則是除以 4,因此 `AAAA` 為 2,`BBBB` 為 1。 但是我還沒理解為什麼要做 `& ~1UL`,相當於限制左移位元數為偶數,目前猜測是因為 $d_{m}=a_{m}^2=2^{2m}$,所以 `m` 僅可能為 2 的偶數次方。 #### 嘗試用 ffs / fls 取代 __builtin_clz ffs 會回傳最低有效位元的索引,從 1 開始計算,若 `x` 為 0 則會回傳 0。因此可以不斷將 `x` 右移 `ffs(x)` 個位元,直到 `x` 為 0 時就停止,最後累計的位移次數減一就會是最高有效位元的索引。 因此加入以下程式碼 ```c int tmp = x, msb = 0; while (tmp) { int index = ffs(tmp); tmp >>= index; msb += index; } ``` 並更改迴圈條件為 ```diff - for (int m = 1UL << ((31 - __builtin_clz(x)) & ~1UL); m; m >>= 2) { + for (int m = 1UL << ((msb - 1) & ~1UL); m; m >>= 2) { ``` 而 fls 是直接回傳最高有效位元的索引 ,因此更改如下 ```diff - for (int m = 1UL << ((31 - __builtin_clz(x)) & ~1UL); m; m >>= 2) { + for (int m = 1UL << ((fls(x) - 1) & ~1UL); m; m >>= 2) { ``` ### 測驗 `3` 此程式碼目的是取得輸入值 `i` 在二進位表示中最高有效位元的索引。 版本一是將 `i` ($2^n$)不斷右移一個位元直到為 0,此時右移次數即為 n,迴圈執行次數也為 n。 版本二則是依照 `i` 的大小,提供四種右移的方式,只要 `i` 大於 $2^k$,就一次右移 k 個位元,讓迴圈的執行次數可以小於 n。 ```c static size_t ilog2(size_t i) { size_t result = 0; while (i >= 2^16) { result += 16; i >>= 16; } while (i >= 2^8) { result += 8; i >>= 8; } while (i >= 2^4) { result += 4; i >>= 4; } while (i >= 2) { result += 1; i >>= 1; } return result; } ``` 版本三使用了 `__builtin_clz` 改寫,但因為輸入值不能為 0,所以先與 1 做 `|` 運算。 ```c return (31 - __builtin_clz(v | 1)); ``` ### 測驗 `5` 此程式碼以測驗三做延伸,計算$\lceil log_2(x) \rceil$。 <!-- 一開始的 `x-1` 是為了讓 --> 若 (x > 0xFFFF) 回傳 1,則 `r` 的值為 `10000`,因此會將 `x` 右移 16 個位元。 ```c r = (x > 0xFFFF) << 4; x >>= r; ``` 對應到測驗三的 ```c while (i >= 2^16) { result += 16; i >>= 16; } ``` 而因為每次左移的位元數都不同,代表 `r` 和 `shift` 不會有相同位元都為 1 的情況,因此計算 result 的方式可以用 `r |= shift` 代替。 ```c shift = (x > 0xFF) << 3; x >>= shift; r |= shift; shift = (x > 0xF) << 2; x >>= shift; r |= shift; ``` 最後再判斷 `x` 是否大於 1。 ```c shift = (x > 0x3) << 1; x >>= shift; return (r | shift | x > 1) + 1; ``` #### 改進程式碼 最直覺的想法就是當 `x` 為 0 時減 0,其他情況減 1。目前採用下方的方法。 ```diff - x--; + x -= (x > 0) ``` ## 第 4 週測驗題 ### 測驗 `1`