# 2023q1 Homework2 (quiz2)
contributed by < `csm1735` >
## 測驗 `1`
### 解釋程式碼原理
此題關鍵在於找出最高位元的 1 ,並將從此位元到最低位元的所有位元 set 為 1 ,再透過對 `x + 1` 來使其進位成 2 的冪的值。
舉例來說 `x` 如果是 $(0010$ $1010)_2$ ,需要先 set 成 $(0011$ $1111)_2$ ,再透過 `x + 1` 成為
$(0100$ $0000)_2$
而因 `x` 為 64 位元的 `uint64_t` ,故最多需要右移 63 個位元。
因此程式碼為:
```c
uint64_t next_pow2(uint64_t x)
{
x |= x >> 1;
x |= x >> 1;
x |= x >> 1;
x |= x >> 1;
x |= x >> 1;
x |= x >> 1;
x |= x >> 1;
x |= x >> 8;
x |= x >> 16;
x |= x >> 32;
return x + 1;
}
```
並可進一步精簡為:
```c
uint64_t next_pow2(uint64_t x)
{
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x |= x >> 32;
return x + 1;
}
```
### 用 `__builtin_clzl` 改寫
```c
uint64_t my_next_pow2(uint64_t x) {
int shift = 64 - __builtin_clzl(x);
return 1UL << shift;
}
```
`__builtin_clzl` 的功能為找出最高位的 1 前面總共有幾個 0
而透過 `shift = 64 - __builtin_clzl(x)` 可以算出 x 在二進位下的 bits 長度,那只要對 1 左移 shift 個位元,即可得到答案。
舉例來說, `x` = $(0010$ $1010)_2$ ,則 `shift` = 64 - 58 = 6 ,因此我們對 1 左移 6 個位元,即可得到答案 $(0100$ $0000)_2$
### 對應的 x86 指令
```
my_next_pow2:
.LFB16:
.cfi_startproc
endbr64
bsrq %rdi, %rdi
movl $64, %ecx
movl $1, %eax
xorq $63, %rdi
subl %edi, %ecx
salq %cl, %rax
ret
.cfi_endproc
```
產生對應的 x86 指令,可以看到 `bsrq` 指令。
---
## 測驗 `2`
### 解釋程式碼原理
此題關鍵在於 `len` 這個變數,代表的是當前 `i` 值在二進制表示下的 bits 長度,也影響到了 `ans` 每次需要左移幾個位元,以 `ans = 1 , i = 2` 為例,由於 `i` 的二進位表示為 $(10)_2$ ,也就是 `len` 為2,因此需先將`ans` 左移 2 位成 $(100)_2$ ,再將 `i` 的值 $(10)_2$ set 上去成 $(110)_2$ 。
而以下的 if 判斷影響到了 `len` 的值。
```c
if (!(DDDD))
len++;
```
可以發現在 `i` 在增加為 2 的冪次時,長度也會增加,例如 `i` 從 `111` 變為 `1000` 時,因此這邊需要判斷 `i` 是否為 2 的冪次,正確程式碼應如下:
```c
if (!(i & i - 1))
len++;
```
如果 `i` 為 2 的冪次, `(i & i - 1)` 將為 0 ,即 `!(i & i - 1)` 為 1 。
接著則是將 `ans` 左移 `len` 個,並 set 上 `i` 的值。
```c
ans = (i | (ans << len)) % M;
```
### 嘗試使用 `__builtin_{clz,ctz,ffs}` 改寫
```c
int myConcatenatedBinary(int n)
{
const int M = 1e9 + 7;
int len = 0;
long ans = 0;
for (int i = 1; i <= n; i++) {
len = 32 - __builtin_clz(i);
ans = (i | (ans << len)) % M;
}
return ans;
}
```
`__builtin_clz` 的功能為找出最高位的 1 前面總共有幾個 0
透過 `shift = 32 - __builtin_clz(x)` 可以算出 x 在二進位下的 bits 長度,也就是變數 `len` 的值,即可用其來算出 `ans`
---
## 測驗 `3`
```c
const uint64_t *end = qword + len >> 3;
```
這邊將 `len` 右移了 3 個位元,也就是等同於除以八,因為我們為了增加執行速度,需要一次處理 8 bytes ,這樣做可以算出能完整放入 64 bits 的共有幾組,至於不足 8 bytes 的則之後處理。
```c
count = (1 << 3) * (len / 8) - count;
```
`(1 << 3) * (len / 8)` 用來計算出總長度中能完整放入 64 bits 的 8 bytes 組共有幾個 bytes ,然後再減去 `count` (即 continuation bytes 的數量)。
```c
count += (len & 7) ? count_utf8((const char *) end, len & 7) : 0;
```
最後再透過 `len & 7` 檢查是否有不足 8 bytes 的部份需要處理,如果有則透過 `count_utf8` 來計算出扣除 continuation bytes 的字元數量,然後再相加以得到結果。
---
## 測驗 `4`
### 解釋程式碼原理
```c
bool is_pattern(uint16_t x)
{
if (!x)
return 0;
for (; x > 0; x <<= 1) {
if (!(x & 0x8000))
return false;
}
return true;
}
```
上述程式碼一開始會先檢查 `x` 是否為 0 ,如果是則直接 `return 0;` ,之後迴圈內會將 `x` 左移一個位元,如果 `x` 為 0 則跳出迴圈,可發現如果 `x` 不為 0 且最高位不為 1 則會 `return false;` ,因此我們可看出 `x` 只有在以下形式會 `return true;`
```
1000 0000 0000 0000
1100 0000 0000 0000
1110 0000 0000 0000
.
.
.
1111 1111 1111 1111
```
根據此性質,可以做以下的改寫
```c
bool is_pattern(uint16_t x)
{
const uint16_t n = ~x + 1;
return (n ^ x) < x;
}
```
將 `~x + 1` (即 `-x`) 和 `x` 做 `xor` 並判斷是否會小於 `x`
這邊以 8 bits 情況下的 $(1110$ $0000)_2$ 和 $(1110$ $1010)_2$ 來當例子:
$(1110$ $0000)_2$ `xor` $(0010$ $0000)_2$ = $(1100$ $0000)_2$
這邊會將原先最低位的 1 給改成 0 ,所以會小於原本的 `x`
$(1110$ $1010)_2$ `xor` $(0001$ $0110)_2$ = $(1111$ $1100)_2$
這邊會將原先中間的 0 給改成 1 ,所以會大於原本的 `x`