# 2023q1 Homework2 (quiz2)
contributed by < `Damien-Chen` >
## 測驗一
### 說明
考慮 next_pow2 可針對給定無號 64 位元數值 x,找出最接近且大於等於 2 的冪的值,例如:
`next_pow2(7)` = 8
`next_pow2(8)` = 8
`next_pow2(13)` = 16
以 `next_pow2(7)` = 8 當例子。
7~(10)~ = 0000 0000 0000 0111~(2)~
我們只要將 0000 0000 0000 0111 + 1 = 0000 ... 1000 = 8~(10)~
再看 `next_pow2(13)` = 16
13~(10)~ = 0000 0000 0000 1101~(2)~
為了得到 0000 0000 0001 0000,可以進行以下操作
當 `x |= x >> 1` 後,結果為 : 0000 0000 0000 1111
再經過第二行 `x |= x >> 1` 後,結果仍不變。
那如果往右移 16 個位元呢? 其結果仍為不變。
也就是說,當以最高位元為起點往右看,所有 bits 都為 1 時 ,無論再向右位移多少 bits,結果都會是 0000 0000 0000 1111,所以只要在最後將其加 1 再 return 即可,可推得 CCCC = x + 1;
而往右移的方式則是依照1,2,4,8,16,32方式向右。所以推得 AAAA = 1,BBBB = 32。
由於 power of 2 必定為只有一個1,其餘全為0,該演算法的邏輯在於將其 MSB 右邊全部設為1,再加1。
```c
int64_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 >> AAAA;
x |= x >> 16;
x |= x >> BBBB;
return CCCC; // CCCC = x + 1
}
```
而要使用 `__builtin_clzl` 改寫,需先對其功能進行了解。
> `int __builtin_clz (unsigned int x)`
> Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined.
> `int __builtin_clzl (unsigned long)`
> Similar to `__builtin_clz`, except the argument type is unsigned long.
`__builtin_clzl` 回傳 leading zero 的個數,舉例來說, `__builtin_clzl(7)` = 61,代表有 61 個連續的 leading zero,那剩下共有 64 - 61 = 3 個 bit 不為 0,那麼只需要將 2 乘 3 次即可。
再看 `__builtin_clzl(13)` = 60,剩下 4 個 bits 不為連續的零,將 2 乘 4 次 即可。
```c
uint64_t next_pow2(uint64_t x)
{
int n = __builtin_clzl(x);
int non_zero = 64 - n;
return pow(2, non_zero);
}
```
---
## 測驗二
```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 (!(DDDD))
len++;
ans = (i | (EEEE)) % M;
}
return ans;
}
```
根據註解提示 : if it is power of 2, we increase the bit length。
因此 DDDD 為判斷是否為2的冪的式子,而2個冪代表二進位表示法中,只有一個1,其餘皆為0,因此,我們可以將 i 和 i - 1 進行 AND 運算,如果為0,代表是2的冪,反之則不是,原理如下:
假設 i = 8~(10)~ = 1000~(2)~
則 8 & (8 - 1) = 1000 & 0111 = 0000 = 0~(10)~
若 i = 7~(10)~ = 0111~(2)~
則 7 & (7 - 1) = 0111 & 0110 = 0110 = 6~(10)~
接著往下看,如果是 2 的冪我們就增加位元長度,但是這位元長度到底是要做什麼呢?
我們舉 n = 4 當作例子看看:
* i = 1 : 不是 2 的冪,因此 ans = 1;
* i = 2 : 是 2 的冪,此時 len = 2,而我們要把 10 接在 1 的後面,因此把1往左位移兩個位元。
* i = 3 : 不是 2 的冪,此時 len = 2,把 11 接在 10 後面,因此要再往左位移兩個位元。
* i = 4 : 是 2 的冪,此時 len = 3,把 100 接在後面,因此需要往左位移三個位元。
從上述的例子可以發現,每次遇到2的冪,我們要往左位移的位元就需要加一,因為每一個更大的2的冪都需要更多的位元來表示,因此可以推得 EEEE = ans << len。
接著由於往左位移,多出來的位元會補0,再與該 i 的值進行邏輯 OR 運算即可將其加再後面。
:::danger
注意書寫規範:中英文間用一個半形空白字元分隔。
:notes: jserv
:::