# 2024q1 Homework4 (quiz3+4)
contributed by < `mesohandsome` >
## quiz3 測驗 1
### 程式碼運作原理
$$
c_m = P_{m + 1} 2^{m + 1}
$$
$$
d_m = (2^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 - 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 = \dfrac{1}{2}P_{m + 1} 2^{m + 1} + (2^m)^2 = \left.
\begin{cases}
c_m / 2 + d_m & \text{if } a_m = 2^m \\
0 & \text{if } a_m = 0
\end{cases}
\right.
$$
$$
d_{m - 1} = (2^{m - 1})^2 = \dfrac{1}{4}(2^m)^2 = \dfrac{1}{4}d_m
$$
在程式碼中,`int m` 對應到 $d_m$,所以每回合都除以 4,也就是右移 2 位。
`int z` 則是 $c_m$,除以 2 也就是右移 1 位。
```c
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;
}
```
使用第 2 週測驗中的 `__ffs(x)` 取代 `__builtin_clz`。
```diff
- for (int m = 1UL << ((31 - __builtin_clz(x)) & ~1UL); m; m >>= 2) {
+ for (int m = 1UL << __ffs(x); m; m>>= 2) {
int b = z + m;
z >>= 1;
if (x >= b)
x -= b, z += m;
}
```
在[第 3 週測驗 1](https://hackmd.io/@sysprog/linux2024-quiz3) 中有一部分的敘述標示不清楚,$c_m$ , $d_m$ , $Y_m$ 之間應有間格標示,但在 hackmd 上用 `\\` 換行有些情況沒辦法顯示,所以黏在一起,閱讀時在這部份產生誤解並觀察了非常久才發現問題。
![image](https://hackmd.io/_uploads/rkOZK3zy0.png)
## quiz3 測驗 2
### 程式碼運作原理
為了減少乘除法運算,需要以 bitwise 運算去逼近 $1/10$,而算式必定為 $\dfrac{an}{2^N}$,$N$ 為非負整數,$a$ 為正整數,所以找出 $(\dfrac{1}{8} + \dfrac{1}{2} + 1) \cdot \dfrac{8}{128} = \dfrac{13}{128} \approx 0.1$。
```
(tmp >> 3) + (tmp >> 1) + tmp
```
上面的運算可以看成 $\dfrac{tmp}{8} + \dfrac{tmp}{2} + tmp = \dfrac{13}{8}tmp$
再把 $\dfrac{13}{8}tmp$ 乘以 $\dfrac{8}{128}$ 得到接近 $\dfrac{1}{10} tmp$,也就是 div 的結果。
也因為 $in = div * 10 + mod$,所以將剛才整理好的 $\dfrac{1}{10}tmp$ 以 bitwise 操作乘以 10 倍後,與原始值相減就會得到 mod 的結果。
```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);
```
`q` 計算完會逼近 `0.8 * in`,在計算 `div` 時要讓他接近 `0.1 * in`,所以要右移 3 位。
而 mod 會等於 div 10 的結果乘 10,所以原本的 `q` 為 `0.8 * in` 並將前 3 位多餘的過濾掉,再加上 `0.2 * in` ( 將值為 `0.1 * in` 的 `div` 左移兩位 ) 使值變為 `1.0 * in` 也就是 `div` 的 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));
}
```
## quiz3 測驗 3
### 程式碼運作原理
使用二分搜尋的技巧,找出 $\log_2i$ 為多少,因此依序填入 65536 , 256 , 16 , 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;
}
```
`__builtin_clz` 的輸入不能為 `0`,因此先將輸入 `v` 和 `1` 做 OR 運算,避免為 `0` 的狀況發生。
```c
int ilog32(uint32_t v)
{
return (31 - __builtin_clz(v | 1));
}
```
## quiz3 測驗 4
### 程式碼運作原理
`avg->internal` 表示 $S_t$,`val << avg->factor` 表示 $Y_t$,`avg->weight` 表示 $\alpha$。
`factor` 用來調整可以顯示到多少的精確度。
```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 = 2^{-weight}Y_t + (1 - 2^{-weight})S_{t - 1}
$$
## quiz3 測驗 5
```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 = 0 的狀況,並仍是 branchless
讓程式一開始依據 x 是否為 0 做減法,避免 unsinged integer 減到小於 0 後出現錯誤。
最後回傳時也判斷 x 是否為 0,決定是否加 1。
```diff!
- x--;
+ x -= (x > 0);
...
- return (r | shift | x > 1) + 1;
+ return (r | shift | x > 1) + (x > 0);
```
## quiz4 測驗 1
### 程式碼運作原理
以每 4 個位元為一個單位計算 1 的個數,利用以下公式計算:
$$
x - \lfloor \frac{x}{2} \rfloor - \lfloor \frac{x}{4} \rfloor - \lfloor \frac{x}{8} \rfloor
$$
將輸入 `v` 右移 1 位 ( 除以 2 ),然後 `& 0x77777777` 的用意是,如果較高位的 4 位在位移之後,右移完要移除的位元會變成低位的第 4 個位元,因此以 `0x77777777` ( `01110111 ... 0111` ) 將這些不需要的位元清除,就可以得到每 4 個位元完整的 $\lfloor \frac{x}{2} \rfloor$。
而這個操作總共用了 3 次,取出 $\lfloor \frac{x}{2} \rfloor$ , $\lfloor \frac{x}{4} \rfloor$ , $\lfloor \frac{x}{8} \rfloor$。
```c
n = (v >> 1) & 0x77777777;
v -= n;
```
使用 `v = (v + (v >> 4))` 會得到:
```
B7 (B7+B6) (B6+B5) (B5+B4) (B4+B3) (B3+B2) (B2+B1) (B1+B0)
```
再使用 `0x0F0F0F0F` 做 mask 得到:
```
0 (B7+B6) 0 (B5+B4) 0 (B3+B2) 0 (B1+B0)
```
與 `0x01010101` 相乘完後會在第 24 個位元後得到 B0 + B1 + ... + B7,因此將結果右移 24 個位元即為結果。
```c
v *= 0x01010101;
return v >> 24;
```
以下程式用來找出每個數之間的 hamming distance,透過 xor 將不同的位元標示成 1,再以 popcount 找出總共有幾個 1,程式中使用了兩層迴圈來計算,因此每一組配對會被重複計算到,所以最後應該要將 `total` 除以 2,也就是右移 1 位。
```c
int totalHammingDistance(int* nums, int numsSize)
{
int total = 0;;
for (int i = 0;i < numsSize;i++)
for (int j = 0; j < numsSize;j++)
total += __builtin_popcount(nums[i] ^ nums[j]);
return total >> 1;
}
```
### 撰寫更高效的程式碼
1. 第 0 個 bit 觀察 :
全部都是 1 -> Hamming Distance = 0
2. 第 1 個 bit 觀察 :
1 有一個,其餘皆為 0
可以直接用單個 bit 判斷 Hamming Distance (1)* (numsize - 1)
3. 第 2 個 bit 觀察 :
1 有兩個,其餘皆為 0
Hamming Distance (2)* (numsize - 2)
根據以上規則可以整理成以下程式,比起原本的程式碼更有效率,可通過 [LeetCode 477. Total Hamming Distance](https://leetcode.com/problems/total-hamming-distance/submissions/):
```c
int total = 0;
for (int i = 0; i < 32; i++) {
int count = 0;
for (int j = 0; j < numsSize; j++)
count += (nums[j] >> i) & 1;
total += count * (numsSize - count);
}
return total;
```
![image](https://hackmd.io/_uploads/Hkw-4mFJC.png)
## quiz4 測驗 2
### 程式碼運作原理
$$
popcount(x \& \overline{m}) - popcount(x \& m) = popcount(x \oplus m) - popcount(m)
$$
`popcount(0xAAAAAAAA) = 16` 所以可以得出 `popcount(n ^ 0xAAAAAAAA) - 16`,但為了避免 `n < 0` 所以加上一個 3 的倍數變為 `popcount(n ^ 0xAAAAAAAA) + 23`。
再做下一回合的運算以得到一個小於 3 的數:
```c
n = popcount(n ^ 0x2A) - 3;
```
經過上一回合後,`n` 目前的值介於 `-3 ~ 2`,最後需要針對小於零的狀況加 3,且在原本程式中並沒有將 `n` 從 `unsigned` 轉型成 `int`,由於沒有標示正負的符號,導致 `& 3` 的結果不如預期:
```diff
- return n + ((n >> 31) & 3);
+ return n + (((int)n >> 31) & 3);
```
觀察可移動的方式,`move_masks[0]` , `move_masks[1]` , `move_masks[2]` 為第一排,如果 `board` 與這三個移動方式做 OR 運算,得到 `0x70044444`,可以發現有連成一直線時,會有一個位元被設定成 `7`,為了檢察是否為 `7`,將 `board + 0x11111111` 在和 `0x88888888` 做 AND 運算就可以發現是否有人獲勝。
```c
static const uint32_t move_masks[9] = {
0x40040040, 0x20004000, 0x10000404, 0x04020000, 0x02002022,
0x01000200, 0x00410001, 0x00201000, 0x00100110,
};
/* Determine if the tic-tac-toe board is in a winning state. */
static inline uint32_t is_win(uint32_t player_board)
{
return (player_board + 0x11111111) & 0x88888888;
}
```
先將 `x` 的範圍縮減,`x >> 15` 移除較低的 15 位,`(x & UINT32_C(0x7FFF))` 只保留最低的 15 位。
```c
static inline int mod7(uint32_t x)
{
x = (x >> 15) + (x & UINT32_C(0x7FFF));
/* Take reminder as (mod 8) by mul/shift. Since the multiplier
* was calculated using ceil() instead of floor(), it skips the
* value '7' properly.
* M <- ceil(ldexp(8/7, 29))
*/
return (int) ((x * UINT32_C(0x24924925)) >> 29);
}
```
## quiz4 測驗 3
```
/* The process of deletion in this tree structure is relatively more intricate,
* although it shares similarities with deletion methods employed in other BST.
* When removing a node, if the node to be deleted has a right child, the
* deletion process entails replacing the node to be removed with the first node
* encountered in the right subtree. Following this replacement, an update
* operation is invoked on the right child of the newly inserted node.
*
* Similarly, if the node to be deleted does not have a right child, the
* replacement process involves utilizing the first node found in the left
* subtree. Subsequently, an update operation is called on the left child of th
* replacement node.
*
* In scenarios where the node to be deleted has no children (neither left nor
* right), it can be directly removed from the tree, and an update operation is
* invoked on the parent node of the deleted node.
*/
```
根據函式註解,可以將缺漏的部分補上。
```c
struct xt_node *least = xt_first(xt_right(del));
if (del == *root)
*root = least;
xt_replace_right(del, least);
xt_update(root, xt_right(least));
return;
```
```c
struct xt_node *most = xt_last(xt_left(del));
if (del == *root)
*root = most;
xt_replace_left(del, most);
xt_update(root, xt_left(most));
return;
```
```c
xt_update(root, parent);
```