# 2020q3 Homework3 (quiz3)
contributed by < `shauming1020` >
###### tags: `homework`
## 測驗 1
```c=
int asr_i(signed int m, unsigned int n)
{
const int logical = (((int) -1) OP1) > 0;
unsigned int fixu = -(logical & (OP2));
int fix = *(int *) &fixu;
return (m >> n) | (fix ^ (fix >> n));
}
```
### 解釋上述程式運作原理
- ${-5}{>>}^{arith}1 = -3$,1111...1011 算術右移 1 得 [1]111...1101,[] 為右移後補上的值,而在只有```unsigned```的系統上,[] 內總是為 0
- 從 ```return``` 回推,透過 ```(fix ^ (fix >> n))``` 造出 mask ,並讓 ```(m >> n)``` 所產生的 [] 可以根據不同系統而對應到正確的值
- ```const int logical = (((int) -1) OP1) > 0;```
- 透過右移 $(int) -1 = 0xFFFFFFFF$ 來判斷是 unsigned 或 signed 系統
- 如果為 unsigned 系統,則 [] 值為 0,其右移後的值大於 0 ,logical 被設為 0x00000001
- 因此 OP1 為 >> 1 即可
- ```unsigned int fixu = -(logical & (OP2));```
- 考慮 unsigned 系統右移後,[] 需要補 1 的情形,即 m < 0
- 例如 0x1000 >> 2 = 0x[00]10,而與 mask 做 | 運算後要為 0x[11]10,因此 **mask 可以是 1100**
- 試著驗證看看,當 m < 0 且 logical 為 true 時,fix 為 0x1111
- ```fix >> 2``` 結果為 0x[00]11,再與 fix 做 $\oplus$ 運算得 0x[11]00,為我們預期的 mask
- 而在 signed 或是 m >= 0 的情況下,fix 總是為 0,得到的 mask 仍為 0,與 (m >> n) 做 | 運算後仍可保持預期結果
- 因此 OP2 為 m < 0 即可
## 測驗 2
```c=
bool isPowerOfFour(int num)
{
return num > 0 && (num & (num - 1))==0 &&
!(__builtin_ctz(num) OPQ);
}
```
> Built-in Function: int __builtin_ctz (unsigned int x)
> Returns the number of trailing 0-bits in x, starting at the least significant bit position. If x is 0, the result is undefined.
###
- 直接帶入 4 的次方數字去回推,例如 num 為 4 = 0x00000100、16 = 0x00010000、64 = 0x01000000
- ```(num & (num - 1))==0```
- 0x0100 & 0x0011 為 0,從操作看就是去判斷 num 中是否只有一個 1 bit
- ```!(__builtin_ctz(num) OPQ)```
- 可以觀察到 4 的次方數值尾數都是**偶數個 0**,判斷是否為偶數可以透過與 mask **0x1** 做 & 運算來判斷,如果不為偶數的話,則 & 0x1 的結果為 1,因此需要取完 not 後才是為偶數個 0 的情況
- OPQ 為 & 0x1
## 測驗 3
Leetcode [1342. Number of Steps to Reduce a Number to Zero](https://leetcode.com/problems/number-of-steps-to-reduce-a-number-to-zero/)
> Given a **non-negative integer** num, return the number of steps to **reduce it to zero**. If the current number is even, you have to **divide it by 2**, otherwise, you have to **subtract 1** from it.
```c=
int numberOfSteps (int num)
{
return num ? AAA + 31 - BBB : 0;
}
```
> Built-in Function: 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.
> population count 簡稱 popcount 或叫 sideways sum,是計算數值的二進位表示中,有多少位元是 1
###
- 從題目給定的描述,如果當前 num 為偶數,則 / 2 (>> 1),否則減 1
- 根據題目的測資觀察,Input num = 8 = 0x0000100[0]
- LSB 為 0 ,即偶數,>> 1 得 0x0000010[0],直到 LSB 不為 0,num = 0x0000000[1],減去 1
- 共 4 次操作
- 因此透過 >> 1 逐一把 LSB 設成 0,而要將 LSB 設成 0 的情況只有在 LSB 為 1 的時候,所以我們只要知道說**至少要右移幾次**,而每次右移後**又至少額外要執行減 1 的動作**
- 31 - __builtin_clz(num) 可以知道至少要右移幾次
- __builtin_popcount(num) 表示有幾個 1 要被減成 0
### 善用 clz
#### LeetCode 1009. Complement of Base 10 Integer
> For a given number N in base-10, return the complement of it's binary representation as a base-10 integer.
> Example 1:
> Input: 5
Output: 2
Explanation: 5 is "101" in binary, with complement "010" in binary, which is 2 in base-10.
```c=
int bitwiseComplement(int N){
if (N == 0) return 1;
int sum = 0, sr = 31 - __builtin_clz(N);
for (int i = 0; i < sr; i++, N >>= 1)
sum += !(N & 0x1) << i;
return sum;
}
```
- 這題和測驗 2 很像,先利用 31 - __builtin_clz(N) 計算出需要右移的次數,接著找出 N 當中所有為 0 的 bit,計算在 complement 時所應表示的值,加總起來即為結果
- 例如 $N = 101$,$N' = 010$,N 的 0 在 補數 N' 中表示為 $2^{1}$
- 因此透過對 N 右移 sr 個 bit,判斷當前的 LSB 是否為 0,如果為 0 的話則在 complement 中表示為 $2^{sr}$
- 將所有 $2^{sr}$ 加總起來即為結果
- 而當 N 為 0,編譯器會先出現 ```runtime error: passing zero to clz(), which is not a valid argument (solution.c)``` 的警示,因此需要額外考慮 N = 0 的情況
![](https://i.imgur.com/oRGNyr1.png)
#### 41. First Missing Positive
> Given an unsorted integer array, find the **smallest** missing **positive** integer.
> Example 1:
Input: [1,2,0]
Output: 3
>Example 2:
Input: [3,4,-1,1]
Output: 2
>Example 3:
Input: [7,8,9,11,12]
Output: 1
## 測驗 4
```c=
#include <stdint.h>
uint64_t gcd64(uint64_t u, uint64_t v) {
if (!u || !v) return u | v;
int shift;
for (shift = 0; !((u | v) & 1); shift++) {
u /= 2, v /= 2;
}
while (!(u & 1))
u /= 2;
do {
while (!(v & 1))
v /= 2;
if (u < v) {
v -= u;
} else {
uint64_t t = u - v;
u = v;
v = t;
}
} while (XXX);
return YYY;
}
```
參考[Binary GCD Algorithm](https://iq.opengenus.org/binary-gcd-algorithm/) 和 [Greatest Common Divisor 特性和實作考量](https://hackmd.io/@sysprog/gcd-impl#Binary-GCD)
- ```
for (shift = 0; !((u | v) & 1); shift++) {
u /= 2, v /= 2;
}
```
- 每執行一次 for 迴圈就從 u 和 v 中提出 2,而最後會提出 $2^{shift}$ 之後再做輾轉相除法
- 直到 u 或 v 當中有一個不為偶數
> 3. If x and y are both even, gcd(x, y) = 2*gcd(x/2, y/2) because 2 is a common divisor.
> Multiplication with 2 can be done with bitwise shift operator.
- ```
while (!(u & 1))
u /= 2;
do {
while (!(v & 1))
v /= 2;
```
- 考慮剩下的三種情形即特性
> 4. If **x is even and y is odd**, gcd(x, y) = gcd(x/2, y).
> 5. On similar lines, if **x is odd and y is even**, then
> gcd(x, y) = gcd(x, y/2). It is because 2 is not a common divisor.
> 6. If **both x and y are odd**, then gcd(x, y) = gcd(|x-y|/2, y)
- ```
if (u < v) {
v -= u;
} else {
uint64_t t = u - v;
u = v;
v = t;
}
} while (XXX);
```
- 取出 u 和 v 的**差值 t**,而如果 v 本身比 u 還大的話,減去 u 將自己當作差值
- 直到**差值 t** 為 0 (u = v or u = 0),而差值 t 由變數 v 所存,因此**XXX 為 v**
> Repeat steps until **x = y**, or until x = 0.
- ```return YYY;```
- 由於在前面已先提出共同有的 $2^{shift}$,因此最後回傳 u 時還要在乘上 $2^{shift}$,故 YYY 為 u << shift
> the GCD is power(2, k) * y, where power(2, k) is 2 raise to the power of k and k is the number of common factors of 2 found in step 2.
## 測驗 5
```c=
#include <stddef.h>
size_t naive(uint64_t *bitmap, size_t bitmapsize, uint32_t *out)
{
size_t pos = 0;
for (size_t k = 0; k < bitmapsize; ++k) {
uint64_t bitset = bitmap[k];
size_t p = k * 64;
for (int i = 0; i < 64; i++) {
if ((bitset >> i) & 0x1)
out[pos++] = p + i;
}
}
return pos;
}
```
可用 ctz 改寫為效率更高且等價的程式碼:
```c=
#include <stddef.h>
size_t improved(uint64_t *bitmap, size_t bitmapsize, uint32_t *out)
{
size_t pos = 0;
uint64_t bitset;
for (size_t k = 0; k < bitmapsize; ++k) {
bitset = bitmap[k];
while (bitset != 0) {
uint64_t t = KKK;
int r = __builtin_ctzll(bitset);
out[pos++] = k * 64 + r;
bitset ^= t;
}
}
return pos;
}
```
- 先觀察 naive
- 假設 bitset = 0x00101100 = bitmap[k=1],p = 1 * 64
- ```for (int i = 0; i < 64; i++) {``` 搜尋整個 bitset
- ```if ((bitset >> i) & 0x1)``` 不斷將 bitset 右移判斷,如果 bitset 的 LSB 為 1
- ```out[pos++] = p + i;``` 則將該位置 p + i 放到 out 中
- 所以該函式在找 bitmap 中所有 bit 為 1 的位置
- 接下來觀察 ctz 版本
- 宣告 ```uint64_t bitset;``` 一次存放一組 bit array,當 bitset 不為 0,表示當中存在 bit 1
- 假設 bitset = 0x00001010,則 ```int r = __builtin_ctzll(bitset);``` 會計算 bitset 尾端有幾個 0,此時 r = 1,即可知道位於 k * 64 + 1 的位置有 bit 1,**而矩陣 index 從 0 起算,因此尾端有幾個 0 恰可直接當作 index**
- ```bitset ^= t;``` 將 bitset 與 mask t 進行 $\oplus$ 運算,而先前已經計算過 [] 中的 1 ( $bitset = 0x000010[1]0$ ),因此希望造出的 mask 可讓 [] 設成 0,並保留其他地方,如此一來 $bitset' = 0x000010[0]0$,即可用 ctz 計算出下一個 1 的位置
- 這樣的 mask 有很多種,考慮 $\oplus$ 與 0 運算可保留原有的 bit,而與 1 運算則是對 bit 取 not 的特性,期望的 mask t 可以為 $0x000000[1]0$
- 在測驗 3 的描述中提到 "x & -x,將 x 的 LSB 取出 (isolate LSB)",這裡應是想表達**取出 bit 1 當中最低位者**,例如 $0x00001010 \& 0x11110110 = 0x00000010$
- 因此透過 **bitset & -bitset** 即可造出預期的 mask t