# 2020q1 Homework (quiz6) contributed by < `IepIweidieng` > ## 正題 ([quiz6](<https://hackmd.io/@sysprog/linux2020-quiz6>)) 原程式碼: ```cpp double divop(double orig, int slots) { if (slots == 1 || orig == 0) return orig; int od = slots & 1; double result = divop(orig / D1, od ? (slots + D2) >> 1 : slots >> 1); if (od) result += divop(result, slots); return result; } ``` ### D2 首先是 `od ? (slots + D2) >> 1 : slots >> 1`。 先思考有哪些位元運算有除法的數學意義。 `slots >> n` 的數學意義是對有號整數 `slots` 以 $2^n$ 進行 floor division 的結果,即 $\left\lfloor\dfrac{slots}{2^n}\right\rfloor$。 而使用 `(slots + ((1 << n) - 1)) >> n` 可取得對有號整數 `slots` 以 $2^n$ 進行 ceil division 的結果,即 $\left\lceil\dfrac{slots}{2^n}\right\rceil$。 可以發現 `od ? (slots + D2) >> 1 : slots >> 1` 符合對 `slots` 以 $2^1$ 進行 ceil division 的模式。 因此猜測 `D2` 為 `1`。 可以發現寫為 `divop(orig / D1, (slots + 1) >> 1)` 就足夠了。 #### 除法補充 另外,改用普通的除法則會變成 $\textrm{sign}\left(\dfrac{slots}{2^n}\right) \cdot \left\lfloor\left|\dfrac{slots}{2^n}\right|\right\rfloor$ 或是 $\textrm{sign}\left(slots\right) \cdot \left\lfloor\textrm{sign}\left(slots\right) \cdot \dfrac{slots}{2^n}\right\rfloor$。 其中當 `slots` 為 32 bit 整數時,$\textrm{sign}\left(slots\right) \cdot x$ 可用 `((slots >> 31) ^ x) - (slots >> 31)` 或 `(slots >> 31) ^ (x + (slots >> 31))` 計算之。 ($-x$ 可用 `~x + 1` 或 `~(x - 1)` 計算之) 因此 `slots / (1 << n)` 可用 ```cpp ((slots >> 31) ^ ((((slots >> 31) ^ slots) - (slots >> 31)) >> n)) - (slots >> 31) ``` ```cpp (slots >> 31) ^ ((((slots >> 31) ^ (slots + (slots >> 31))) >> n) + (slots >> 31)) ``` 或是 ```cpp const int32_t sign = slots >> 31; // ... (sign ^ (((sign ^ slots) - sign) >> n)) - sign // or sign ^ (((sign ^ (slots + sign)) >> n) + sign) ``` 計算之。 ### D1 再來,因為這個函式的功能是將浮點數除以整數的除法,可以假設這個函式正確運行時,$\textrm{divop}\left(orig,\ slots\right) = \dfrac{orig}{slots}$ 成立。 假設 `slots` 為 `2` 的乘冪,那麼 `result` 會直接是內層 `divop()` 呼叫的結果,因此需要保持 `slots` 與 `orig` 的比值。 為了保持原先的比值,既然 `slots` 變為一半,那麼 `orig` 也該變為一半。 因此猜測 `D1` 為 `2`。 ### 回加 ```cpp if (od) result += divop(result, slots); ``` 因為前面用了 ceil division,所以直接計算的結果會變小,要加回來。 而要加多少,可以數學方式進行驗證。 這裡將 `result` 分為剛初始化的 $result_{init}$ 與加回來後的 $result_{final}$ 兩個值以做區別,其中 $result_{final} = \textrm{divop}\left(orig,\ slots\right) = \dfrac{orig}{slots}$。 $$ \begin{split} & \text{Assume } \left\{ \begin{array}{c} D_1 = 2 \\ D_2 = 1 \\ \end{array} \right. \\ & result_{init} \\ &= \textrm{divop}\left(orig / D_1,\ od\ ?\ \left(slots + D_2\right) >> 1 : slots >> 1\right) \\ &= \textrm{divop}\left(orig / 2,\ od\ ?\ \left(slots + 1\right) >> 1 : slots >> 1\right) \\ &= \textrm{divop}\left(\dfrac{orig}{2},\ \left\lceil\dfrac{slots}{2}\right\rceil\right) \\ &= \dfrac{orig}{2} \div \left\lceil\dfrac{slots}{2}\right\rceil \\ \end{split} $$ #### 當 `slots` 可被 `2` 整除時 這時 $\left\lceil\dfrac{slots}{2}\right\rceil = \dfrac{slots}{2}$。 $$ \begin{split} & result_{init} \\ &= \dfrac{orig}{2} \div \left\lceil\dfrac{slots}{2}\right\rceil \\ &= \dfrac{orig}{2} \div \dfrac{slots}{2} \\ &= orig \div slots \\ &= \dfrac{orig}{slots} \\ &= result_{final} \\ \end{split} $$ 無須回加,符合原程式。 #### 當 `slots` 不可被 `2` 整除時 這時 $\left\lceil\dfrac{slots}{2}\right\rceil = \dfrac{slots + 1}{2}$。 $$ \begin{split} & result_{init} \\ &= \dfrac{orig}{2} \div \left\lceil\dfrac{slots}{2}\right\rceil \\ &= \dfrac{orig}{2} \div \dfrac{slots + 1}{2} \\ &= orig \div \left(slots + 1\right) \\ &= \dfrac{orig}{slots + 1} \\ \end{split} $$ 接著計算要加的量。 $$ \begin{split} & result_{final} - result_{init} \\ &= \dfrac{orig}{slots} - \dfrac{orig}{slots + 1} \\ &= orig \cdot \left(\dfrac{1}{slots} - \dfrac{1}{slots + 1}\right) \\ &= orig \cdot \left(\dfrac{slots + 1}{slots \cdot \left(slots + 1\right)} - \dfrac{slots}{slots \cdot \left(slots + 1\right)}\right) \\ &= orig \cdot \left(\dfrac{1}{slots \cdot \left(slots + 1\right)}\right) \\ &= \dfrac{orig}{slots \cdot \left(slots + 1\right)} \\ &= \dfrac{orig}{slots + 1} \div slots \\ &= result_{init} \div slots \\ &= \textrm{divop}\left(result_{init},\ slots\right) \\ \end{split} $$ 符合原程式,因此猜測正確。$\Box$ ## 改寫為 Tail Recursion 形式 原程式: ```cpp double divop(double orig, int slots) { if (slots == 1 || orig == 0) return orig; int od = slots & 1; double result = divop(orig / 2, od ? (slots + 1) >> 1 : slots >> 1); if (od) result += divop(result, slots); return result; } ``` 將原本的 `result` 的初始化移到 `if (od)` branches 中,然後 inline `od`。 ```cpp double divop(double orig, int slots) { if (slots == 1 || orig == 0) return orig; if (slots & 1) { const double result = divop(orig / 2, (slots + 1) >> 1); return result + divop(result, slots); } return divop(orig / 2, slots >> 1); } ``` ```cpp double divop(double orig, int slots) { if (slots == 1 || orig == 0) return orig; if (slots & 1) return divop(orig / 2, (slots + 1) >> 1) + divop(divop(orig / 2, (slots + 1) >> 1), slots); return divop(orig / 2, slots >> 1); } ``` 新增參數 `owe` (欠) 以做回加。 ```cpp static double divop3(double orig, int slots, double owe) { if (slots == 1 || orig == 0) return orig + owe; if (slots & 1) return divop3(orig / 2, (slots + 1) >> 1, owe + divop3(divop3(orig / 2, (slots + 1) >> 1, 0), slots, 0)); return divop3(orig / 2, slots >> 1, owe); } double divop(double orig, int slots) { return divop3(orig, slots, 0); } ```