---
# System prepended metadata

title: 2020q3 Homework3 (quiz3)

---

#  2020q3 Homework3 (quiz3)
contributed by < [`Noahnut`](https://github.com/Noahnut) >

### 測驗 1
第一題主要是要實作跨平台的 ASR
在不支援 `signed` right shift 的環境下，right shift 前面的位數是會補零，會導致結果與預期的不符，所以目標會是要將前面補 0 的位元反轉成 1 ，但如果要能夠跨平台就不能單純將前面為 0 的值補成 1。

測驗 1 的第一題為判斷現有環境是否不支援 `signed` 的 right shift，我們知道 `-1` 在 32 位元的架構下為 `0xffffffff`，如果在不支援 `signed` 的 right shift 的話，-1 就會變成正數。
所以 `OP1` 為 `>> 1`
`OP2` 從 `return` 那邊反推可以發現是 `m` right shift n bits 並與 `fix ^ (fix >> n)` 的 OR 運算，在這個題目是要將因 right shift 而補 0 的位元換成 1，所以 `fix` 在 `m` 為負數的情況下必須為 -1，而從 `OP1` 可以知道在不支援 `signed` 的 right shift 環境下， logical 會為 1，所以 `OP2` 要為 `m < 0`，判斷目前的計算是為在不支援 `signed` 的 right shift 環境下且 `m` 為負。

`OP1 = >> 1`
`OP2 = (m < 0)`
```c=
int asr_i(signed int m, unsigned int n)
{
    const int logical = (((int) -1) >> 1) > 0;
    unsigned int fixu = -(logical & (m < 0));
    int fix = *(int *) &fixu;
    return (m >> n) | (fix ^ (fix >> n));
}
```

### 測驗 2
測驗 2 是要去實作判斷一個數是否為 4 的次方。

`(num & (num - 1))` 是用來判斷，如果這個數字是 4 的次方，轉成 2 進位後，必定只有一個字元為 1，而如果將這個數字減 1 ，二進位的這個數字就會退位。
例如：
4 為 `00000100` ， 4 - 1 為 3 (`0000011`)，而將這兩個數字做 and 操作就會為零。
這個操作就可以篩選掉非為 4 的倍數的數字。
觀察一下 4 的次方在二進位的規則
4  `00000100`
16 `00010000`
64 `01000000`
可以發現從尾巴到第一個出現的 1 ，出現的 0 都會是 2 的倍數，利用  `__builtin_ctzl` 這個函式來得到從最尾數到第一個 1 的數，如果非為 4 的次方跟 `0X1` 做 & 運算就會為 1。
```cpp
bool isPowerOfFour(int num)
{
    return num > 0 && (num & (num - 1))==0 &&
           !(__builtin_ctzl(num) & 0x1);  
}
```

### [LeetCode 1009. Complement of Base 10 Integer](https://leetcode.com/problems/complement-of-base-10-integer)
```c=
int bitwiseComplement(int N){
     if (N == 0) {
         return 1;
     } 
    unsigned int mask = 0xFFFFFFFF; 
    int leadingZeroNumber = __builtin_clz(N);
    mask = mask >> leadingZeroNumber;
    return ~N & mask;
}
```
這邊的解題想法為，只要將現有的數做補數運算，其餘原先的 leading Zero 不做改變，所以用 `__builtin_clz` 就可以算出原先的 leading Zero 多少，然後再將 `0xFFFFFFFF` 做 right shift leading Zero 的數量，在與 ~N 做運算就可以將因 NOT 運算的變成 1 的 leading Zero 變成 0。

### [LeetCode 41. First Missing Positive](https://leetcode.com/problems/first-missing-positive/)



### Golomb-Rice coding






### 測驗 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) 
題目的意思是，給一32位元的 int 的數，給出需要多少次運算才能將這個數變成 0，而且在運算這個數的時候有以下條件，當這個正整數目前為偶數時，將其除於 2，當其為奇數的時候將其減 1。

如果很直覺的來想這個題目的解法的話，會是以下邏輯
> 數的奇偶用其 LSB 跟 `0x1` 做 & 運算來判斷
1. 如果數為偶數，就將其 right shift 等同於對這個數除於 2 
2. 如果數為奇數，就將其 減 1 

```cpp
int numberOfSteps (int num){
    int count= 0;
    while (num != 0) {
        if (num & 0x1) {
            count++;
            num ^= 0x1;
        } else {
            count++;
            num = num >> 1;
        }
    }
    return count;
}
```

所以從上面這個邏輯來看，如果位元為 1 必定代表一次運算，因為要減 1，而在減 1 的運算下，並不會讓位元有任何移動，如果要讓數為 0 就必須要退位，所以答案就會是整個 int 位元為 1 的數加上這個數在 32 位元中佔幾位。
AAA 就為 `__builtin_popcount(num)` 代表這個數中有多少個 1 位元
BBB 就為 `__builtin_clz(num)` 代表這個數從右邊數來要多少個 0 才會遇到 1。
個人覺得改寫成這樣比較直觀
```c=
return num ? 31 - __builtin_clz(num) + __builtin_popcount(num)  : 0;
```
```c=
int numberOfSteps (int num)
{
    return num ? __builtin_popcount(num) + 31 - __builtin_clz(num) : 0;
}
```

### 測驗 4
在閱讀過 [Binary GCD Algorithm](https://iq.opengenus.org/binary-gcd-algorithm/) 和 [Greatest Common Divisor 特性和實作考量](https://hackmd.io/@sysprog/gcd-impl) 後可以知道，GCD 的 x 與 y 在不同奇偶下會有不同特性。

1. x 跟 y 都為偶數

 $gcd(x, y) = 2 * gcd(x/2, y/2)$

2. x 為偶數，y 為奇數

 $gcd(x, y) = gcd(x/2, y)$
 
3. x 為奇數，y 為偶數

$gcd(x, y) = gcd(x, y/2)$

4. x 為奇數，y 為奇數

$gcd(x, y) = gcd((x - y)/2), y)$

並在搭配 $gcd$ 的性質
* $gcd(x, y) = gcd(y, x)$
* $gcd(x, 0) = |a|$
* $gcd(x, y) = gcd(x, x \% y)$

來閱讀測試的程式
```c 
for (shift = 0; !((u | v) & 1); shift++) {
        u /= 2, v /= 2;
}
```
這一段的程式主要是在判斷 `u` 與 `v` 是否都是偶數，如果都是就將其除與 2 並根據第一個性質，所以我們要把除於 2 的次數存在 `shift` 的這個變數。

```c 
while (!(u & 1))
        u /= 2;
.....
while (!(v & 1))
        v /= 2;
```
當 `u` 或 `v` 為偶數時，根據性質，要單獨將其除於 1。
在 `while` 中的運算就為輾轉相除法的部分，而 `XXX` 的部分，會發現 `v` 會是 `u - v` 的值，所以如果當滿足 `u = v` 時，`v` 就會為零所以 `XXX` 會是 `v`。
而 `YYY` 從第一個性質可以知道最後我們算出來的 `u` 還必須在乘於在一開始我們除的 2 的倍數才會是我們的最大公因數，所以 `YYY` 是 `u << shift`。

### 測驗 5
這題的目的是要找到所有為 1 的 bit 在這個 bit array 的位置。
在最原本的實作中是對每個 bitset 去向右 shift 並對其與 `0x1` 做 & 運算，如果為 1 就將其位置記錄下來。
而效率更高的做法為利用 `__builtin_ctzll()` 計算從 LSB 到第一個出現的 1 中 0 的數量，所以當我算完一個 1 的位置，就要讓其變成 0，這樣才能找到下一個 1 的位置，所以 KKK = `bitset & -bitset` 並將其與 ` bitset ^= t` 做運算就可以將最右邊為 1 的 bit 變成 0。