# 2024q1 Homework4 (quiz3+4)
contributed by < `yan112388` >
## 2024q3 第 3 週測驗題
### 測驗一
[連結](https://hackmd.io/@sysprog/linux2024-quiz3)
**程式碼解釋**
計算非負整數 `N` 的平方根
1. **版本一**:
使用 `log2(N)` 含式來計算 `N` 的以 2 為底的對數,並將結果轉換為整數,儲存在 `msb` 中,藉此得到 `N` 的最高非零位的位置
**版本二**:
使用 `while` 迴圈,將數字 `N` 右移(相當於將數字除以 2),直到剩下最高的非零位元,得到該位置
3. 計算 `a = 1 << msb`,這等同於設置 `a` 為 2 的 `msb` 次方,此值將作為初始步長
4. `result` 被初始化為 0,將用來儲存最終的平方根
5. 使用`while` 迴圈,只要 `a` 不為 0,迴圈就會繼續。在每次的迭代中:
a. 它檢查 `(result + a) * (result + a)` 是否小於或等於 `N`,相當於檢查 `(result + a)` 的平方是否小於或等於 `N`
b. 如果上述條件成立,那麼 `a` 被加到 `result` 上,因為 `(result + a)` 仍然是 `N` 的平方根的下界
c. 無論上述條件是否成立,`a` 都會被右移一位,相當於將 `a` 除以 2,藉此將搜尋的步長做調整
5. 當 `a` 變為 0 時,迴圈結束,此時 `result` 包含了 `N` 的平方根的整數部分
6. 回傳 `result`
**版本三**:
以 Digit-by-digit calculation 方式,數學式的原理與細節尚未搞懂,待釐清。
### 測驗二
目的:計算 `in` 除以 10 的商和餘數,並將結果儲存在 `div` 和 `mod` 中
程式碼如下:
```c
uint32_t x = (in | 1) - (in >> 2); /* div = in/10 ==> div = 0.75*in/8 */
```
```
(in | 1) - (in >> 2) = in + 1 - (in >> 2)
= in + 1 - (in / 4)
= 0.75 * in + 1
```
* 欲計算 `in` 除以 10,10 可以表達為 `1.25 * 8`,所以我們可以先計算` 0.75 * in`,接著除以 8,就可以得到`in` 除以 10 的近似值
* `(in | 1)` 亦即 `in + 1`,作用為將 `in` 的最低位元設為 1。
* 如果 in 是奇數, 則 `(in | 1) = in`
* 如果 in 是偶數,則 `(in | 1) = in + 1`
可以將此操作看成是一種數值的修正,計算` 0.75 * in` 時,實際上是在計算 `3 * in / 4`。當 `in` 是奇數時,此算數為精確的。但是當 `in` 是偶數時,由於我們進行了除法並向下取整數,結果會比實際的 `0.75 * in` 略小,加 1 可以補償這個誤差。
```c
uint32_t q = (x >> 4) + x;
x = q;
q = (q >> 8) + x;
q = (q >> 8) + x;
q = (q >> 8) + x;
q = (q >> 8) + x;
```
* 每一行 `q = (q >> 8) + x` 都相當於將 `q` 除以 256,256 可以表達為 2 的 8 次方。在此有 4 行,表示將 x 除以 2 的 32 次方。
```c
*div = (q >> 3);
*mod = in - ((q & ~0x7) + (*div << 1));
```
* 為了得到商數,將 `q` 右移 3 個位元,即提取 q 的高位元作為商。
```
(*div << DDDD) ≈ in / 10 * 2
= 0.2 * in+
```
* 計算餘數部分,希望計算 in 除以 10 的餘數,於是我們需要從 in 中減去 *div * 10`((q & ~0x7) + (*div << 1))` 亦同於 `div * 8 + *div * 2`
### 測驗三
計算以 2 為底的對數
**版本二**:
``` c
static size_t ilog2(size_t i)
{
size_t result = 0;
while (i >= 65536) {
result += 16;
i >>= 16;
}
while (i >= 256) {
result += 8;
i >>= 8;
}
while (i >= 16) {
result += 4;
i >>= 4;
}
while (i >= 2) {
result += 1;
i >>= 1;
}
return result;
}
```
使用了二分搜法的想法
* 檢查輸入是否大於等於 65536($2^{16}$)
* 如果是,將結果加上 16,並將 `i` 右移 16 位,代表著將其範圍縮小到原來的 1/65536
重複此過程,檢查輸入是否大於等於 256($2^8$)、16 ($2^4$) 及 $2$。每次檢查成功,就將結果加上相應的值($8、4、1$),並將 `i` 右移相應的位數。
**版本三**
``` c
int ilog32(uint32_t v)
{
return (31 - __builtin_clz(v | 1));
}
```
* 利用了 GCC 的內建函數 `__builtin_clz`,其功能為找到 x 最高位數前面有幾個 0 , x 為 0 時,未定義。
* 對於一個 32 位的數字 v,`31 - __builtin_clz(v)` 就等於 v 的最高非零位的位置,也就是 `floor(log2(v))`。
* 為了處理 `__builtin_clz(0)` 是未定義的這種問題,使用了 `v | 1` 而非 `v`。 `v | 1` 的作用是將 `v` 的最低位設為 1,確保了進入 __builtin_clz is never的輸入永遠非0。
### 測驗四
EWMA 數學式展開如下:
\begin{aligned}
S_t &= \alpha Y_t + (1 - \alpha) \cdot S_{t-1} \\
&= \alpha Y_t + (1 - \alpha)(\alpha Y_{t-1} + (1 - \alpha) \cdot S_{t-2}) \\
&= \alpha Y_t + (1 - \alpha)(\alpha Y_{t-1} + (1 - \alpha)(\alpha Y_{t-2} + (1 - \alpha) \cdot S_{t-3})) \\
&= \alpha (Y_t + (1 - \alpha) Y_{t-1} + (1 - \alpha)^2 Y_{t-2} + \ldots + (1 - \alpha)^k Y_{t-k} + \ldots + Y_0)
\end{aligned}
```c
struct ewma *ewma_add(struct ewma *avg, unsigned long val)
{
avg->internal = avg->internal
? (((avg->internal << avg->weight) - avg->internal) +
(val << avg->factor)) >> avg->weight
: (val << avg->factor);
return avg;
}
```
* $S_t$ 表示時間 $t$ 時的EWMA值,對應程式碼中的 `avg->internal`
* $Y_t$ 表示時間 $t$ 時的資料點,對應程式碼中的 `val`
* $\alpha$ 為一係數,用來控制新樣本值的權重,即 $(1 - 2^{-weight})$,
流程:
* 先判斷 internal 是否為零
* 若為零則表示這是第一個資料點,將 `val` 值乘以 $2^{factor}$,作為初始的 EWMA 值 `(val << avg->factor)`
* 非零則代表已經有資料點被加入到 EWMA 公式中
* 將 `internal` 值乘以 $2^{weight}$ 再減去 `internal` ,得到$(2^{weight} - 1) \cdot S_{t-1}$ ,對應於公式中的$(1 - \alpha) \cdot S_{t-1}$
* `val << avg.factor` 將新的樣本值 `val` 左移 `avg->factor` 位,相當於將其乘以 $2^{factor}$ 進行縮放,對應於公式中的 $\alpha Y_t$
* 將前述兩項相加,得到 $(2^{weight} - 1) \cdot S_{t-1} + 2^{factor} \cdot Y_t$,最後將結果右移 `avg->weight` 位,相當於除以 $2^{weight}$
**Q:為甚麼 $(2^{weight} - 1) \cdot S_{t-1}$ 會對應於公式中的 $(1 - \alpha) \cdot S_{t-1}$ ?**
欲求 $(1 - \alpha) \cdot S_{t-1}$,將之展開,得到:
\begin{aligned}
(1 - \alpha) \cdot S_{t-1}
&= (1 - (1 - 2^{-weight})) \cdot S_{t-1} \\
&= 2^{-weight} \cdot S_{t-1}
\end{aligned}
程式碼中:
\begin{aligned}
(2^{weight} - 1) \cdot S_{t-1}
&= (2^{weight} - 2^0) \cdot S_{t-1} \\
&= (2^{weight} - 2^{weight} \cdot 2^{-weight}) \cdot S_{t-1} \\
&= 2^{weight} \cdot (1 - 2^{-weight}) \cdot S_{t-1} \\
&= 2^{weight} \cdot \alpha \cdot S_{t-1} \\
\end{aligned}
最後,將 $(2^{weight} \cdot \alpha \cdot S_{t-1})$ 除以 $2^{weight}$:
\begin{aligned}
\frac{2^{weight} \cdot \alpha \cdot S_{t-1}}{2^{weight}}
&= \alpha \cdot S_{t-1} \\
&= (1 - 2^{-weight}) \cdot S_{t-1}
\end{aligned}
## 2024q3 第 4 週測驗題