# 2024q1 Homework4 (quiz3+4)
contributed by < [`padaray`](https://github.com/padaray) >
## quiz3
### 測驗 `1`
#### 計算平方根
**版本一:**
對輸入的參數 `N` 以 2 為底數取 log,意義是當我們將 `N` 取 log2 時,可以得到 most significant bit (msb)。
對 1 左移 msb 個位元後存入變數 `a` ,意義是找出一個必定大於 `N` 的平方根的數,利用此數來逼近找到 `N` 的平方根。
最後使用 while 迴圈,計算 `a + result` 的平方是否大於 `N`,若小於 `N` 則將此數存入 `result` 變數,對 `a` 右移 1 bit,繼續計算,直到 `a` = 0。
</br>
**版本二:**
版本二的差別是不使用 `math.h` 的 `log2` 函式,直接對參數 `N` 進行右移,右移至參數為 0 時,則代表找到 most significant bit。此方法將程式的可移植性增強,並且不影響原本的函式原型 (prototype) 和精度 (precision)
```diff
- int msb = (int) log2(N);
+ int msb = 0;
+ int n = N;
+ while (n > 1) {
+ n >>= 1;
+ msb++;
+ }
```
</br>
**版本三:**
完整程式碼(無遮蔽處)
```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;
}
```
`__builtin_clz(x)` :是 gcc 內建處理二進位的其中一個函式巨集,此函式會回傳 counting leading zero,幫助我們找到 most significant bit。
在 for 迴圈中左移 `31 - __builtin_clz(x)` bit 數得到 msb,`& ~1UL` 可以確保最後一個 bit 為 0,理由是變數 `m` 必須是偶數對應到 $d_n = (2^n)^2$。而 m 右移兩個 bit 是對應到 $d_{m-1} = d_m/4$
</br>
### 測驗 `2`
#### 針對正整數在相鄰敘述進行 mod 10 和 div 10 操作,如何減少運算成本?
除法原理:$f=g \times Q + r$
* $f$: 被除數
* $g$: 除數
* $Q$: 商
* $r$: 餘數
程式碼如下:
```c
carry = tmp / 10; // carry 代表商(Q)
tmp = tmp - carry * 10; // tmp 代表餘數(r)
```
如果我們想要使用 bitwise operation 來實作除法,如果被除數包含了除了 2 以外的因數,就會造成結果不精確,所以使用以下方式。
假設除數為 10,我們必須找到 $\dfrac{1}{10} \approx \dfrac{a}{2^N}$,最後發現是 $\approx \dfrac{13}{128}$
程式碼如下:
```c
unsigned d2, d1, d0, q, r;
d0 = q & 0b1;
d1 = q & 0b11;
d2 = q & 0b111;
q = ((((tmp >> 3) + (tmp >> 1) + tmp) << 3) + d0 + d1 + d2) >> 7;
r = tmp - (((q << 2) + q) << 1);
```
`(((tmp >> 3) + (tmp >> 1) + tmp) << 3)` 使用 bitwise operation 取得一個分子有 13 的分數,因此用 $\dfrac{tmp}{8} + \dfrac{tmp}{2} + tmp = \dfrac{13tmp}{8}$,之後再左移三位消掉分母 8。
`d0`、`d1`、`d2` 是為了將 `tmp` 右移時損失的 bit 數加回,加回後再右移 7 位也就是除以 128 得到商數。
`(((q << 2) + q) << 1)` 相當於 $(q \times 4 + q) \times 2$,也就是最一開始程式碼的 `carry * 10`。
最終程式碼可包裝為以下:
```c
#include <stdint.h>
void divmod_10(uint32_t in, uint32_t *div, uint32_t *mod)
{
uint32_t x = (in | 1) - (in >> 2); /* div = in/10 ==> div = 0.75*in/8 */
uint32_t q = (x >> 4) + x;
x = q;
q = (q >> 8) + x;
q = (q >> 8) + x;
q = (q >> 8) + x;
q = (q >> 8) + x;
*div = (q >> 3);
*mod = in - ((q & ~0x7) + (*div << 1));
}
```
</br>
### 測驗 `3`
#### 考慮 ilog2 可計算以 2 為底的對數,且其輸入和輸出皆為整數。
**版本一:**
初始化變數 `log` 為負一,直接對參數 `i` 進行右移,每次右移將 `log++`,右移至參數 `i` 為 0 時
</br>
**版本二:**
完整程式碼(無遮蔽處)
```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;
}
```
為了避免數字過大每次位移一個 bit 太慢,例如:65536 需要位移 16 個 bit,因此加入條件判斷,若大於特定 2 的冪次方,直接移動該次方的位元數。
</br>
**版本三:**
```c
int ilog32(uint32_t v)
{
return (31 - __builtin_clz(v));
}
```
</br>
### 測驗 `4`
#### 解釋 EWMA 的實作
Exponentially Weighted Moving Average (EWMA) 是一種統計方法,透過對每筆資料給予不同的權重計算平均,通常越新的資料權重就越高,越舊的資料權重越小,好處是可以適應新的資料變化,又保留一定的歷史資料影響。
數學定義:
$S_t = \begin{cases}
Y_0, & t=0 \\
\alpha Y_t + (1-\alpha)\cdot S_{t-1}, & t > 0
\end{cases}$
$\alpha$ : 舊資料加權降低的程度,越高代表降低越快。介於 0 ~ 1 之間
$Y_t$ : 時間 t 時的資料
$S_t$ : 時間 t 時算出的 EWMA
以下為程式碼實作:
```c
* ewma_add() - Exponentially weighted moving average (EWMA)
* @avg: Average structure
* @val: Current value
*
* Add a sample to the average.
*/
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;
}
```
`internal` 代表目前計算的 ewma (數學式中的 $S_t$) , `(avg->internal << avg->weight)` 代表數學式中的 $\alpha = \dfrac{1}{2^{weight}}$ (**但不知道原因為何**),因此 $1- \alpha = \dfrac{2^{weight} - 1}{2^{weight}}$。
當 `internal` 為 0 時,`internal` 代入 `(val << avg->factor)` 寫成公式為 $2^{factor} \cdot val$。
當 `internal` 不為 0 時,`internal` 代入 `(((avg->internal << avg->weight) - avg->internal) +
(val << avg->factor)) >> avg->weight` 寫成公式為 $\dfrac{2^{weight}-1}{2^{weight}} \cdot internal + \dfrac {2^{factor}}{2^{weight}} \cdot val$,轉換一下可以得到 $(1-\alpha) \cdot internal + {2^{factor}} \cdot \alpha \cdot val$
</br>
### 測驗 `5`
#### 延伸測驗 `3` 使用下方程式碼計算 $\lceil \log_2 x\rceil$
```c=
int ceil_ilog2(uint32_t x)
{
uint32_t r, shift;
x--;
r = (x > 0xFFFF) << 4;
x >>= r;
shift = (x > 0xFF) << 3;
x >>= shift;
r |= shift;
shift = (x > 0xF) << 2;
x >>= shift;
r |= shift;
shift = (x > 0x3) << 1;
x >>= shift;
return (r | shift | x > 1) + 1;
}
```
第五、六行判斷參數 `x` 是否大於 0xFFFF,意義是確定 `x` 的 msb 是否位於高位的 16 bit,若為真,將 `x` 右移 16 bit。
第七、八、九行判斷參數 `x` 在這剩下的 16 bit 中,msb 是否位於高位的 8 bit,若為真,將 `x` 右移 8 bit,將右移的位數紀錄在 `r` 中。
第十行到第十四行依此類推。
第四、十五行將 `x` 先 -1 後 + 1 回傳是為了處理 2 的冪次方,若不處理會讓 2 的冪次方多計算一次,例:$2^7$ 經過無條件進位法,會變成 8,因此要先減 1
</br></br>
## quiz4
### 測驗 `1`
#### [popcount(population count)](https://en.wikichip.org/wiki/population_count)