# 2024q1 Homework4 (quiz3+4)
contributed by < `shiang1212` >
## [第三週測驗題](https://hackmd.io/@sysprog/linux2024-quiz3)
### 測驗一
這個測驗的目的是計算 `input` 的平方根(捨去小數),最簡單的方法是從 `reslut = 0` 開始測驗,測驗 `reslut * reslut > input` 是否成立,成立的話,此時的 `reslut - 1` 就是我們要的答案;不成立的話就 `reslut++` 繼續往上測驗。
與版本一和版本二的概念相似,兩方法的測驗方法如下:
```c
int a = 1 << msb;
int result = 0;
while (a != 0) {
if ((result + a) * (result + a) <= N) // 測驗
result += a;
a >>= 1;
}
```
相較於簡單的方法用 `reslut++`,這個方法是用 `reslut += 2^n` 逼近,其中 `n = {msb, msb - 1, msb - 2, ..., 1, 0}`,測驗 `reslut + 2^n * i + 2^n <= N` 是否成立,成立的話就 `reslut += 2^n`。每輪測驗完就讓 `n--`,直到 `n == 0`,以下為範例。
```
input = 50
msb = 5
-----------------------------------------
result = 0, a = 32 (2^5) | 測驗不成立,a >>= 1
result = 0, a = 16 (2^4) | 測驗不成立,a >>= 1
result = 0, a = 8 (2^3) | 測驗不成立,a >>= 1
result = 0, a = 4 (2^2) | 測驗成立,result += a, a >>= 1
result = 4, a = 2 (2^1) | 測驗成立,result += a, a >>= 1
result = 6, a = 1 (2^0) | 測驗成立,result += a, a >>= 1
result = 7, a = 0 | a == 0,結束測驗
```
### 測驗三
#### 版本一:
透過不斷對輸入進行右移,計算輸入的 leftmost set bit 位置。
```
nth bit 7654 3210
14: 0000 1110
^
leftmost set bit 在第 3 個 bit => log(14) = 3
```
考慮到輸入為 $2^0 = 1$ 的情況,所以 `log` 初始成 -1。但也可以改寫成以下:
```diff
int ilog2(int i)
{
- int log = -1;
+ int log = 0;
- while (i) {
+ while (i > 1) {
i >>= 1;
log++;
}
return log;
}
```
#### 版本二:
使用二分搜尋的作法,不斷切半判斷輸入是否大於 $2^{16}、2^{8}、2^{4}、2^{2}、2^{1}$ 並右移 16、8、4、2、1。使用二分搜尋的概念讓該方法可以得到與版本一相同的答案,但運算速度比版本一更快。
```
若 "|" 左側數值大於 0,則進行右移
0110 1001 0111 0001|1110 1111 0000 0110
(右移16,result+=16)
0000 0000 0000 0000 0110 1001 0111 0001
------------------------------------------
0000 0000 0000 0000 0110 1001|0111 0001
(右移8,result+=8)
0000 0000 0000 0000 0000 0000 0110 1001
------------------------------------------
0000 0000 0000 0000 0000 0000 0110|1001
(右移4,result+=4)
0000 0000 0000 0000 0000 0000 0000 0110
------------------------------------------
0000 0000 0000 0000 0000 0000 0000 01|10
(右移2﹑result+=2)
0000 0000 0000 0000 0000 0000 0000 0001
------------------------------------------
0000 0000 0000 0000 0000 0000 0000 000|1
("|" 左側數值小於 0,不右移)
0000 0000 0000 0000 0000 0000 0000 0001
------------------------------------------
result = 30 // leftmost set bit
```
#### 版本三:
```c
int ilog32(uint32_t v)
{
return (31 - __builtin_clz(v));
}
```
相似於版本一,只是反過來計算輸入的 leading zero 個數,反推 leftmost set bit 的位置。
```
0000 0000 0000 0000 1110 1111 0000 0110
leading zero 個數:16
leftmost set bit = 31 - 16 = 15 // 第 15 個 bit
```
### 測驗五
```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;
}
```
## [第四週測驗題](https://hackmd.io/@sysprog/linux2024-quiz4)
### 測驗一
```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 >> AAAA;
}
```
這段程式碼計算 `nums` 裡任兩個有號整數的漢明距離(Hamming distance),使用兩個 `for` 迴圈走訪 `nums` 裡的所有數值,並計算每一種組合的漢明距離。
[wiki](https://zh.wikipedia.org/zh-tw/%E6%B1%89%E6%98%8E%E8%B7%9D%E7%A6%BB) 中漢明距離的介紹:
>兩個等長字符串之間的漢明距離(英語:Hamming distance)是兩個字符串對應位置的不同字符的個數。
程式碼的第 6 行用於計算漢明距離,首先 `nums[i]` 與 `nums[j]` 做 XOR 操作可以得到一串由 0 和 1 組成的數列,其中 1 代表兩個數值二進位下對應的 bit 不同,再用 `__builtin_popcount` 求該數列 bit 為 1 的個數,就可以得到 `nums[i]` 與 `nums[j]` 的漢明距離。
```
num_1 = 10100
num_2 = 01111
-----------------
// 兩者做 XOR 得到 num_3
num_3 = 11011
-----------------
// 計算 num_3 的 bit 為 1 的個數
hamming_distance = __builtin_popcount(num_3)
// hamming_distance = 4,即為 num_1 與 num_2 二進位對應 bit 不同的個數
```
了解如何計算兩數值之間的漢明距離後,只要將所有組合的漢明距離加總就能達到該題的要求。但該程式碼在累加的時候會重複計算,舉例來說,在計算 `num_1` 與 `num_2` 之間的漢明距離時,程式碼會額外多累加一次 (即`hamming_distance(num_1, num_2)` 與 `hamming_distance(num_2, num_1)`),所以最後要再除以 2,也就是右移 1,所以 `AAAA = 1`。