# 2020q3 Homework4 (quiz4)
contributed by < `zzzxxx00019` >
> [2020q3 第 4 週測驗題](https://hackmd.io/@sysprog/2020-quiz4)
## 測驗 1
### 題目:
[LeetCode 461. Hamming Distance](https://leetcode.com/problems/hamming-distance/) 提及,兩個整數間的 Hamming distance 為其二進位的每個位元的差。請計算輸入參數兩整數 x 與 y 的 Hamming distance,例如整數 1 的二進位為 0 0 0 1,而整數 4 的二進位為 0 1 0 0,則 1 與 4 的 Hamming distance 為 2。
```
1 (0 0 0 1)
4 (0 1 0 0)
[ ] [ ]
| |
\_ 2 _/
```
運用第 3 周測驗 提到的 __builtin_popcount (gcc extension 之一),我們可實作如下:
```cpp
int hammingDistance(int x, int y) {
return __builtin_popcount(x OP y);
}
```
請補完程式碼。
### 原理:
根據題目說明,兩個整數間的 [Hamming distance](https://en.wikipedia.org/wiki/Hamming_distance) 為其二進位的每個位元的差 ( 距離 ) ,並將每個位元間的距離加總,因此在距算每個位元間的距離,透過 `xor` 運算是最合理的,要計算每位元距離加總,其實就是去算兩數做 `xor` 後,共有幾個 `bit 1` ,因此透過 `__builtin_popcount` 去做計算,根據上述說明,判斷 `OP : ^`
### 延伸問題:
1. 舉出不使用 gcc extension 的 C99 實作程式碼
* 參考 [quiz3](https://hackmd.io/@sysprog/2020-quiz3) 中所提供的 `popcnt_branchless` 實作程式碼來改寫:
```gcc=
int popcnt_branchless(uint32_t v)
{
uint32_t n;
n = (v >> 1) & 0x77777777;
v -= n;
n = (n >> 1) & 0x77777777;
v -= n;
n = (n >> 1) & 0x77777777;
v -= n;
v = (v + (v >> 4)) & 0x0F0F0F0F;
v *= 0x01010101;
return v >> 24;
}
int hammingDistance(int x, int y)
{
return popcnt_branchless(x ^ y) ;
}
```
![](https://i.imgur.com/ArNZPnW.png)
2. 練習 [LeetCode 477. Total Hamming Distance](https://leetcode.com/problems/total-hamming-distance/),你應當提交 (submit) 你的實作 (C 語言實作),並確保執行結果正確且不超時
* 根據題目所提供的三筆數字做觀察 4 、 14 、 2 ,它們的二進制表示分別為 `0100` 、 `1110` 、`0010` ,假設從右數第二個 `bit` 來做觀察的話,三數依序分別為 `0` 、 `1` 、 `1` ,如此總共會有 $1\times 2$ 種組合
* 要算出 `Total Hamming Distance` 的方法,就是把所有數中,每個 `bit` 的所有組合加總
* 假設每個 `bit` 中,共有 $m$ 個 $0$ 與 $n$ 個 $1$ ,且輸入為 `32 bit` 的正整數,可以寫成以下的數學表事法:
$TotalHammingDistance=\sum_{k=0}^{31}(m_k)\times(n_k)$
```cpp=
int totalHammingDistance(int* nums, int numsSize)
{
int distance = 0 ;
int bit1[32] = {0} , bit0[32] = {0} ;
for (int i = 0 ; i < numsSize ; i++){
for (int j = 0 ; j < 32 ; j++){
( nums[i] >> j ) & 1 ? bit1[j]++ : bit0[j]++ ;
}
}
for (int i = 0 ; i < 32 ; i++)
distance += bit1[i] * bit0[i] ;
return distance ;
}
```
![](https://i.imgur.com/TX4KD9F.jpg)
* 但這樣的寫法需要透過兩個 `array` 去存放 `bit 0` 跟 `bit 1` 的數量,明顯造成空間上的浪費,且在檢查每個 `bit` 上,是透過迴圈依序右移並做 `&1` ,步驟明顯較為冗長,因此做以下改正:
* 共有 $numsSize$ 個數,則在第 $k$ 個位元,滿足 $numsSize = m_k + n_k$
* 將上述結果套用,$TotalHammingDistance=\sum_{k=0}^{31}(m_k)\times(numsSize - m_k)$
* 根據上面的結果,目前只需要尋找 `bit 1` 的位置,因此改用 `__builtin_ctz` 來取代迴圈
* 在 `__builtin_ctz` 的操作上,參考 [quiz3 測驗 5](https://hackmd.io/@jT29vQ4-SxmlBU5UMj1TGA/quiz3#%E6%B8%AC%E9%A9%97-5) 的部分,原理相似
```cpp=
int totalHammingDistance(int *nums, int numsSize)
{
if (!nums || !numsSize) return 0 ;
int distance = 0 ;
int bit1[32] = {0} ;
for (int i = 0 ; i < numsSize ; i++){
while(nums[i]){
bit1[ __builtin_ctz(nums[i]) ] ++ ;
nums[i] ^= nums[i] & -nums[i] ;
}
}
for (int i = 0 ; i < 32 ; i++ ){
distance += bit1[i] * (numsSize - bit1[i]) ;
}
return distance ;
}
```
![](https://i.imgur.com/Da7m7pV.png)
3. 研讀錯誤更正碼簡介及抽象代數的實務應用,摘錄其中 Hamming Distance 和 Hamming Weight 的描述並重新闡述,舉出實際的 C 程式碼來解說:
* 透過 `encoder-decoder` 的方式,在資料加入一些數學結構,這些數學結構可以協助在一定數量的資料錯誤內進行資料修正
![](https://i.imgur.com/dSkAkNh.png)
* Hamming distance & Hamming weight
* 透過兩個向量 $x=(0001011)$ 與 $x'=(1101010)$ 來舉例說明
* Hamming distance 就是它們不一樣的 bit 有幾個,這個例子 $d_h(x,x')=3$
* Hamming weight 是指一個向量不是 0 的地方有幾個,所以 $w_H(x)=3$ 、 $w_H(x')=4$
* Hamming code
* `Hamming code` 功能在於資料除錯,**能檢查出 1 位元的錯誤發生與發生錯誤的位置**
* `Hamming code` 長度公式: $M≦2^n$ , $K=n+1$ , $M$ 為資料長度 , $K$ 為檢查位元長度,以 8 bits 資料長度舉例 , $8≦2^3$ ,故 $n=3$ , $K=4$ ,因此加上 `Hamming code` 長度為 `12 bits`
* 透過 $M=(10101101)$ 這 `8 bits` 的資料來說明 `hamming code` 的 `encoder-decoder` 流程
首先先建立 `12 bits` 的表格,並將 $M$ 依序填入表格中, `hamming code` 通常使用到 $2^n$ 的位置,因此必須空下 $2^n$ 的 bit 供錯誤檢查碼填入, `H0 ~ H3` 代表更正碼填入位置
|1 |2 |3 |4 |5 |6 |7 |8 |9 |10|11|12|
|- |- |- |- |- |- |- |- |- |- |- |- |
|H0|H1|1 |H2|0 |1 |0 |H3|1 |1 |0 |1 |
接著將 `bit 1` 的位置做 `XOR` 運算,也就是 `3` 、 `6` 、 `9` 、 `10` 、 `12` 的二進制編碼做相加運算
$HammingCode = (0011)_2\oplus (0110)_2\oplus (1001)_2\oplus (1010)_2\oplus (1100)_2 =(1010)_2$
因此 `H0 ~ H3` 依序為 `1` 、 `0` 、 `1` 、 `0` ,並將其==反填==回表中, $Codeword=(011001011101)$
|1 |2 |3 |4 |5 |6 |7 |8 |9 |10|11|12|
|- |- |- |- |- |- |- |- |- |- |- |- |
|==0== |==1==|1 |==0==|0 |1 |0 |==1==|1 |1 |0 |1 |
若資料傳輸過程中,沒發生任何錯誤,也就是 `Codeword` 的任何 `bit` 沒有損毀,那麼將 `Codeword` 中對應為 `bit 1` 的二進制編碼做 `XOR` 運算,出來結果會為 `0` ,以上述 decoder 的結果為例:
$Check = (0010)_2\oplus (0011)_2\oplus (0110)_2\oplus (1000)_2\oplus (1001)_2\oplus (1010)_2\oplus(1100)_2=(0000)_2$
但若資料傳輸過程發生損毀, hamming code 功能是能檢查出 1 bit 的資料錯誤與位置,假設 bit 5 發生損毀,那麼 Codeword 則變為 $(011011011101)$ ,同樣將 bit 1 位置的二進制做 XOR 運算:
$Check = (0010)_2\oplus (0011)_2\oplus(0101)\oplus (0110)_2\oplus (1000)_2\oplus (1001)_2\oplus (1010)_2\oplus(1100)_2=(0101)_2$
可以發現結果為 $(0101)$ ,代表 `5` 的位置發生錯誤,藉由此機制修正 `1 bit` 的錯誤發生
## 測驗 2
### 題目:
LeetCode [1483. Kth Ancestor of a Tree Node](https://leetcode.com/problems/kth-ancestor-of-a-tree-node/)
```cpp=
#include <stdlib.h>
typedef struct {
AAA;
int max_level;
} TreeAncestor;
TreeAncestor *treeAncestorCreate(int n, int *parent, int parentSize)
{
TreeAncestor *obj = malloc(sizeof(TreeAncestor));
obj->parent = malloc(n * sizeof(int *));
int max_level = 32 - __builtin_clz(n) + 1;
for (int i = 0; i < n; i++) {
obj->parent[i] = malloc(max_level * sizeof(int));
for (int j = 0; j < max_level; j++)
obj->parent[i][j] = -1;
}
for (int i = 0; i < parentSize; i++)
obj->parent[i][0] = parent[i];
for (int j = 1;; j++) {
int quit = 1;
for (int i = 0; i < parentSize; i++) {
obj->parent[i][j] = obj->parent[i][j + BBB] == -1
? -1
: obj->parent[obj->parent[i][j - 1]][j - 1];
if (obj->parent[i][j] != -1) quit = 0;
}
if (quit) break;
}
obj->max_level = max_level - 1;
return obj;
}
int treeAncestorGetKthAncestor(TreeAncestor *obj, int node, int k)
{
int max_level = obj->max_level;
for (int i = 0; i < max_level && node != -1; ++i)
if (k & (CCC))
node = obj->parent[node][i];
return node;
}
void treeAncestorFree(TreeAncestor *obj)
{
for (int i = 0; i < obj->n; i++)
free(obj->parent[i]);
free(obj->parent);
free(obj);
}
```
請補完。
### 原理:
先試著理解題目與整個程式運作模式:
#### 題目理解:
```
Input:
["TreeAncestor","getKthAncestor","getKthAncestor","getKthAncestor"]
[[7,[-1,0,0,1,1,2,2]],[3,1],[5,2],[6,3]]
Output:
[null,1,0,-1]
Explanation:
TreeAncestor treeAncestor = new TreeAncestor(7, [-1, 0, 0, 1, 1, 2, 2]);
treeAncestor.getKthAncestor(3, 1); // returns 1 which is the parent of 3
treeAncestor.getKthAncestor(5, 2); // returns 0 which is the grandparent of 5
treeAncestor.getKthAncestor(6, 3); // returns -1 because there is no such ancestor
```
* 透過陣列來表示每個 `node` 所指向的祖節點, `parent[0]` 代表 `node 0` ...
* `node 0` 為 `root` ,祖節點不存在,為 `-1` ,而 `node 1` 與 `node 2` 的祖節點為 `0` ,以此類推
#### `TreeAncestor` 結構定義:
```cpp=3
typedef struct {
AAA;
int max_level;
} TreeAncestor;
```
* 用來定義 `TreeAncestor` 的資料型態
#### 解讀 `treeAncestorCreate` 程式內容:
```cpp=10
TreeAncestor *obj = malloc(sizeof(TreeAncestor));
obj->parent = malloc(n * sizeof(int *));
```
* 透過 `malloc` 動態分配記憶體,將位址回傳給 `TreeAncestor` 型態的指標 `obj`
* `obj->parent` 被分配用來記錄 `int` 型態指標的記憶體空間
```cpp=13
int max_level = 32 - __builtin_clz(n) + 1;
```
* 透過 `32 - __builtin_clz(n) + 1` 計算右數開始的最高位有效 `bit` ,即 `tree` 的 `max_level`
* 在下面建表的迴圈中,終止存取條件是最後為 `-1` ,若不 `+1` 的話,在單鏈狀態下最後一個 `node` 會將空間會全部剛好使用,導致迴圈無法順利終止
```cpp=14
for (int i = 0; i < n; i++) {
obj->parent[i] = malloc(max_level * sizeof(int));
for (int j = 0; j < max_level; j++)
obj->parent[i][j] = -1;
}
```
* 現在 `parent` 中每個 `int*` 紀錄 `malloc(max_level * sizeof(int))` 所分配的記憶體位址,成為 `parent[n][max_level]` 這樣型態的二維陣列,並將陣列中的所有數值初始化為 `-1`
* 透過二維陣列 `parent[i][j]` ,代表 `node i` 的第 $2^j$ 代祖節點
* 因 parent 被設定為二維陣列的型態,故 `AAA = int** parent`
```cpp=19
for (int i = 0; i < parentSize; i++)
obj->parent[i][0] = parent[i];
```
* 這邊的 `parent[i]` 為一開始的 `int *parent` ,而非 `obj->parent` 的二維陣列
* 初始化陣列狀態,將 `parent[i][0]` 記為上一代祖節點
```cpp=22
for (int j = 1;; j++) {
int quit = 1;
for (int i = 0; i < parentSize; i++) {
obj->parent[i][j] = obj->parent[i][j + BBB] == -1
? -1
: obj->parent[obj->parent[i][j - 1]][j - 1];
if (obj->parent[i][j] != -1) quit = 0;
}
if (quit) break;
}
```
* 所使用的存取邏輯為透過 parent[i][j] ,存取 node i 的第 $2^j$ 的祖節點
* 整個順序可以表示為 `parent[i][j-1] <- parent[i][j] <- parent[i][0]` 這樣的關係
* 因此迴圈是用來檢查 `node i` 的 $2^{j+BBB}$ 代祖先是否存在,如果不存在的話, `node i` 的第 $2^j$ 代祖節點自然也不存在
* 若 `node i` 的 $2^{j+BBB}$ 代祖先存在,代表 `node j` 的祖節點就是 node i 的第 $2^{j+BBB}$ 的祖節點
* 透過此方法可以在建表上省下不少時間,時間複雜度為 $n$ $log(n)$
* 故 `BBB = -1`
#### 解讀 `treeAncestorGetKthAncestor` 程式內容:
```cpp=36
int treeAncestorGetKthAncestor(TreeAncestor *obj, int node, int k)
{
int max_level = obj->max_level;
for (int i = 0; i < max_level && node != -1; ++i)
if (k & (CCC))
node = obj->parent[node][i];
return node;
}
```
* 在不超出 `max_level` 的情況下,透過迴圈來尋找節點
* 如果 `node == -1` ,代表該節點已不存在,也沒往上再找的必要,終止迴圈
* 每尋找一個節點,就將自己本身設為上一代祖節點,因此尋訪方式為依序尋找 $2^0$ 、 $2^1$ 、 $2^2$ ... 以此類推,故 `CCC = 1 << i`
#### 解讀 `treeAncestorFree` 程式內容:
```cpp=45
void treeAncestorFree(TreeAncestor *obj)
{
for (int i = 0; i < obj->n; i++)
free(obj->parent[i]);
free(obj->parent);
free(obj);
}
```
* 將 `n` 個節點所分配的記憶體空間依序刪除
* `obj->n` 在前面未定義也未使用過,需在 `TreeAncestor` 的結構定義上加入 `int n`
* 在 `treeAncestorCreate` 加入 `obj->n = n` 這樣的程式碼
#### 輸入陣列舉例:
假設輸入 `[-1,0,0,1,2,0,1,3,6,1]` 這樣的陣列,則 parent 得到以下的結果:
|[i][j]| 0 | 1 | 2 | 3 |
| - | - | - | - | - |
| 0 |-1 |-1 |-1 |-1 |
| 1 |0 |-1 |-1 |-1 |
| 2 |0 |-1 |-1 |-1 |
| 3 |1 |0 |-1 |-1 |
| 4 |2 |0 |-1 |-1 |
| 5 |0 |-1 |-1 |-1 |
| 6 |1 |0 |-1 |-1 |
| 7 |3 |1 |-1 |-1 |
| 8 |6 |1 |-1 |-1 |
| 9 |1 |0 |-1 |-1 |
```graphviz
digraph
{
0
0 -> 1
0 -> 2
1 -> 3
2 -> 4
0 -> 5
1 -> 6
3 -> 7
6 -> 8
1 -> 9
}
```
### 延伸問題:
在 [LeetCode 1483. Kth Ancestor of a Tree Node](https://leetcode.com/problems/kth-ancestor-of-a-tree-node/) 提交你的實作,你應該要在 C 語言項目中,執行時間領先 75% 的提交者
原本的想法是透過犧牲記憶體空間與建表時間,在 `treeAncestorGetKthAncestor` 的部分達到時間複雜度為 $O(1)$ 的實作,但在 `treeAncestorCreate` 的過程就需要 $O(n^2)$ 的時間複雜度,在 leedcode 上果然 `Time Limit Exceeded` 了,還需要想別的方法改善
```cpp=
TreeAncestor *treeAncestorCreate(int n, int *parent, int parentSize)
{
TreeAncestor *obj = malloc(sizeof(TreeAncestor));
obj->parent = malloc(n * sizeof(int *));
obj->bound = malloc(n* sizeof(int));
obj->n = n;
for (int i = 0; i < n; i++){
obj->parent[i] = malloc((i+1) * sizeof(int));
}
for (int i = 0; i < parentSize; i++)
obj->parent[i][0] = parent[i];
for (int i = 1; i < parentSize ; i++) {
for (int j = 1 ;; j++) {
if(obj->parent[i][j-1] == 0){
obj->bound[i] = j ;
break ;
}
obj->parent[i][j] = obj->parent[ obj->parent[i][j-1] ][0] ;
}
}
return obj;
}
int treeAncestorGetKthAncestor(TreeAncestor *obj, int node, int k)
{
if (k > obj->bound[node]) return -1 ;
return obj->parent[node][k-1] ;
}
```
![](https://i.imgur.com/gmfiyfe.png)
## 測驗 3
### 題目:
考慮貌似簡單卻蘊含實作深度的 FizzBuzz 題目:
* 從 1 數到 n,如果是 3的倍數,印出 “Fizz”
* 如果是 5 的倍數,印出 “Buzz”
* 如果是 15 的倍數,印出 “FizzBuzz”
* 如果都不是,就印出數字本身
觀察 printf 的(格式)字串,可分類為以下三種:
1. 整數格式字串 "%d" : 長度為 2 B
2. “Fizz” 或 “Buzz” : 長度為 4 B
3. “FizzBuzz” : 長度為 8 B
考慮下方程式碼:
```cpp
#define MSG_LEN 8
char fmt[MSG_LEN + 1];
strncpy(fmt, &"FizzBuzz%u"[start], length);
fmt[length] = '\0';
printf(fmt, i);
printf("\n");
```
我們若能精準從給定輸入 i 的規律去控制 start 及 length,即可符合 FizzBuzz 題意:
```cpp
string literal: "fizzbuzz%u"
offset: 0 4 8
```
以下是利用 bitwise 和上述技巧實作的 FizzBuzz 程式碼: (bitwise.c)
```cpp
#define MSG_LEN 8
static inline bool is_divisible(uint32_t n, uint64_t M) {
return n * M <= M - 1;
}
static uint64_t M3 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 3 + 1;
static uint64_t M5 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 5 + 1;
int main(int argc, char *argv[]) {
for (size_t i = 1; i <= 100; i++) {
uint8_t div3 = is_divisible(i, M3);
uint8_t div5 = is_divisible(i, M5);
unsigned int length = (2 << div3) << div5;
char fmt[MSG_LEN + 1];
strncpy(fmt, &"FizzBuzz%u"[(MSG_LEN >> KK1) >> (KK2 << KK3)], length);
fmt[length] = '\0';
printf(fmt, i);
printf("\n");
}
return 0;
}
```
請補完。
### 原理:
* `is_divisible` 的部分透過 [quiz2](https://hackmd.io/@jT29vQ4-SxmlBU5UMj1TGA/quiz2#%E6%B8%AC%E9%A9%97-4) 的快速除法技巧來實作
* `fmt` 這個陣列裡面的內容,決定這次要 `print` 出樣的字串,而 `fmt` 的內容由 `strncpy` 來決定
>strncpy(fmt, &"FizzBuzz%u"[start], length)
* `strncpy` 透過 `offset` 與 `length` 來決定要複製怎麼樣的字串到 `fmt` 裡面,如下表所示:
|倍數 |offset|length|
|- |- |- |
|3 |0 |4 |
|5 |4 |4 |
|15 |0 |8 |
|other|8 |2 |
* 透過 `is_divisible` 檢查 `i` 是否為 `3` 或 `5` 的倍數,並回傳 `1 ( true )` 或是 `0 ( false )`
* `length = (2 << div3) << div5` 透過 `div3` 與 `div5` 決定要右推幾個 `bit` ,當 `i` 為 `15` 的倍數,就代表 `div3` 與 `div5` 結果皆為 `true` ,則 `length = 8`
* `offset` 的部分由 `(MSG_LEN >> KK1) >> (KK2 << KK3)` 一連串的運算來實作
* 代入 `i = 5`,只需要右移 `1` 次,故推測 `KK1 = div5` ,且 `KK2 = div3` ,如此 `KK2 << KK3` 結果必為 `0`
* 代入 `i = 3`,共需要右移 `4` 次,套用 `KK1 = div5` 與 KK2 = div3 的推測, `KK2 << KK3` 的結果需為 `4`, `KK3 = 2` 才能滿足條件
* 代入 `i = 15` , `offset = ( 8 >> 1 ) >> ( 1 << 2 ) = 0` ,上述假設成立
* 代入 `i = 17` , `offset = ( 8 >> 0 ) >> ( 0 << 2 ) = 8` ,上述假設成立
* 故 `KK1 = div5` 、 `KK2 = div3` 、 `KK3 = 2`
### 延伸問題:
評估 `naive.c` 和 `bitwise.c` 效能落差
* 透過迴圈檢查 `0 ~ 1000000` 資料,並透過 perf 分析效能落差
==naive.c==
```
Performance counter stats for './naive.o':
1767.93 msec task-clock # 0.053 CPUs utilized
41 context-switches # 0.023 K/sec
4 cpu-migrations # 0.002 K/sec
54 page-faults # 0.031 K/sec
63,6106,7793 cycles # 3.598 GHz
48,2593,9095 instructions # 0.76 insn per cycle
9,6705,4919 branches # 546.999 M/sec
1232,3274 branch-misses # 1.27% of all branches
33.140408084 seconds time elapsed
0.404676000 seconds user
1.366285000 seconds sys
```
==bitwise.c==
```
Performance counter stats for './bitwise.o':
1664.01 msec task-clock # 0.049 CPUs utilized
19 context-switches # 0.011 K/sec
1 cpu-migrations # 0.001 K/sec
51 page-faults # 0.031 K/sec
62,8016,9210 cycles # 3.774 GHz
47,6773,4807 instructions # 0.76 insn per cycle
9,5679,7062 branches # 574.994 M/sec
1245,0080 branch-misses # 1.30% of all branches
34.217948407 seconds time elapsed
0.439350000 seconds user
1.226187000 seconds sys
```
## 測驗 4
### 題目:
考慮到整數 `PAGE_QUEUES` 可能有以下數值:
* (1 or 2 or 3 or 4)
* (5 or 6 or 7 or 8) × ($2^n$), n from 1 to 14
給定 `size_t offset`,試著將原本運算:
```cpp
#include <stdint.h>
size_t blockidx = offset / PAGE_QUEUES;
```
由於` PAGE_QUEUES` 的數值分佈已知,在整數除法時,可思考加速運算的機制。需要注意,某些硬體平台缺乏整數除法指令 (如 Arm Cortex-A8),即使 Intel 公司出品的 Core 處裡器 [Sandy Bridge 微架構](https://en.wikipedia.org/wiki/Sandy_Bridge)中,針對 64 位元整數的除法運算,會佔用 40 到 103 個處理器週期,運算成本仍屬沈重。
於是我們可預先進行計算,從而避免整數除法指令的使用: (假設在 x86_64 硬體執行 GNU/Linux 並透過 gcc/clang 編譯)
```cpp
#include <stdint.h>
#define ffs32(x) ((__builtin_ffs(x)) - 1)
size_t blockidx;
size_t divident = PAGE_QUEUES;
blockidx = offset >> ffs32(divident);
divident >>= ffs32(divident);
switch (divident) {
CASES
}
```
其中 `CASES` 可能形如:
```cpp
case 2: blockidx /= 2;
break;
```
這樣的組合,請用最少的 case-break 敘述做出同等 `size_t blockidx = offset / PAGE_QUEUES;` 效果的程式碼。
`CASES` 至少該包含哪些數字:
### 原理:
>Built-in Function: int __builtin_ffs (int x)
> * Returns one plus the index of the least significant 1-bit of x, or if x is zero, returns zero.
* `(__builtin_ffs(x)) - 1` 回傳的結果為 x 中最後一個為 1 的位置,換句話說,就是從右數來到第一個 `bit 1` ,共經過幾個 `bit 0` ,也就是可以右移幾次
* 功能等同於 `__builtin_ctz(x)` 不同的地方是 `__builtin_ffs(x)` 允許 `x = 0` 的情況發生
* 透過 `blockidx = offset >> ffs32(divident)` 與 `divident >>= ffs32(divident)` 簡化除法動作,可以透過以下數學表示:
$blockidx = \dfrac{offset}{PageQueues} = \dfrac{\dfrac{offset}{2^n}}{\dfrac{PageQueues}{2^n}}$
* 再透過下表觀察不同的 QUEUE_PAGES ,向右位移後的結果:
* 因為 (5 or 6 or 7 or 8) × ($2^n$), n from 1 to 14 ,皆可直接右移 n 次,因此不列入下表
* 觀察到右移後結果,因此只需要考慮 1 、 3 、 5 、 7 這幾種 case
| QUEUE_PAGES | binary | divident >>= ffs32(divident) |
| - | - | - |
|1 | 0001 | 1 |
|2 | 0010 | 1 |
|3 | 0011 | 3 |
|4 | 0100 | 1 |
|5 | 0101 | 5 |
|6 | 0110 | 3 |
|7 | 0111 | 7 |
|8 | 1000 | 1 |