# 2023q1 第二週 quiz
## 第一題
完整程式碼
```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
x |= x >> 1;
x |= x >> 1;
x |= x >> 1;
x |= x >> 1;
x |= x >> 1;
x |= x >> 1;
x |= x >> 1;
```
不知道其用途,舉實際上的例子來搭配,才不會"舉燭"
取 x = 65 (010001)
```
x |= x >> 1;// x = 01100001
x |= x >> 1;// x = 01110001
x |= x >> 1;// x = 01111001
x |= x >> 1;// x = 01111101
x |= x >> 1;// x = 01111111
x |= x >> 1;// x = 01111111
```
可以發現一連串的操作就是從 most significant bit 開始逐漸填滿 1 ,而後面的
```c
x |= x >> 8;
x |= x >> 16;
x |= x >> 32;
```
取 x = 140737488355328(2^47)
經過前面的 7 次 x |= x>>1 會變成
```
111111110000000000000000000000000000000000000000
```
做
```c
x |= x >> 8;
```
變成
```
111111111111111100000000000000000000000000000000
```
得到 16 個 bit 都是 1 以此類推經過
```c
x |= x >> 16;
```
變成
```
111111111111111111111111111111110000000000000000
```
再用
```c
x |= x >> 32;
```
變成
```
111111111111111111111111111111111111111111111111
```
這樣只要最後的 x + 1 就是大於原先 x 的 2 的最小的冪次方,很適合用於來找分配 memory best fit 要多大
### 用 __builtin_clzl 改寫
clz 就是 count leading zero 的意思,__builtin_clz(8)的返回值就是29,因為8的二進位制表示为0000 0000 0000 0000 0000 0000 0000 1000,其中前導0有2個9。
如果可以算前面有幾個 0 也可以很快地知道, most significant 的 1 在哪邊,可改寫成
```c
uint64_t next_pow2_clz(uint64_t x)
{
return 1<<(64 - __builtin_clzl(x));
}
```
### 當上述 clz 內建函式已運用時,編譯器能否產生對應的 x86 指令?
運用
```
gcc -O2 -std=c99 -S next_pow2.c
```
編譯,可以看到以下 machine code
```
next_pow2_clz:
.LFB23:
.cfi_startproc
endbr64
bsrq %rdi, %rdi
movl $64, %ecx
movl $1, %eax
xorq $63, %rdi
subl %edi, %ecx
sall %cl, %eax
cltq
ret
.cfi_endproc
.LFE23:
```
可以發現,編譯出來的 machine code 並沒有多做額外的 function call,還可以看到 brsq 這個指令
好奇 ctz 的其他類似指令, ctz , ffs 會給哪些 x86指令呢
ctz
```
bsfq
```
## 第二題
解釋 [leet code 1680. Concatenation of Consecutive Binary Numbers](https://leetcode.com/problems/concatenation-of-consecutive-binary-numbers/description/)
```c
int concatenatedBinary(int n){
const int M = 1e9 + 7;
int len = 0; /* the bit length to be shifted */
/* use long here as it potentially could overflow for int */
long ans = 0;
for (int i = 1; i <= n; i++) {
/* removing the rightmost set bit
* e.g. 100100 -> 100000
* 000001 -> 000000
* 000000 -> 000000
* after removal, if it is 0, then it means it is power of 2
* as all power of 2 only contains 1 set bit
* if it is power of 2, we increase the bit length
*/
if (!((i - 1)&i))
len++;
ans = (i | (ans << len)) % M;
}
return ans;
}
```
其中
```c
if (!((i - 1)&i))
len++;
```
是用來判斷是否為 2 的冪次方的判斷是,舉 16 為例
16 的 2 進位
10000
16 - 1 = 15 的 2 進位
01111
不難發現, 2 的冪次方與其 -1 的值完全沒有重複的 bit ,故其做 and operation 後必等於 0。
而
```c
ans = (i | (ans << len)) % M;
```
表示每次要補上新的數字於 ans 後面時, ans 需要向左 shift len 的長度,騰出空間給新的數字
### 嘗試使用 __ builtin_{clz,ctz,ffs} 改寫
如果一個數字為 2 的冪次方,表示其寫成 2 進位必定為
00...001..00,只有一個1,只是不確定 1 座落在哪個 bit
可以用 clz (count leading zero) 跟 ctz (count trailing zero) ,加起來後的值判斷其是不是等於 bit length - 1, 如果是就代表 1 只有出現一次,為 2 的冪次方。
```c
if((__builtin_clz(x) + __builtin_ctz(x)) == 31)
len++;
```
同理也可以用類似的概念 ffs (find first set) 來找到第一個 set (從右邊 lsb 數)的 1 ,搭配 clz 看兩者加起來是否等於 bit length 也會代表它就是 2 的冪次方
```c
if((__builtin_clz(x) + __builtin_ffs(x)) == 32)
len++;
```