# 2020q3 Homework3 (quiz3)
contributed by <`hsiehong`>
###### tags: `進階電腦系統理論與實作`
### [題目](https://hackmd.io/@sysprog/2020-quiz3)
## Outline
[TOC]
## 測驗1
對有號整數的跨平台 ASR 實作
```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));
}
```
`OP1 = >> 1`, `OP2 = m < 0`
* `logical` 是用來判斷系統負數的位移是補 0 或 1,若系統是補0 , -1 右移 1 後會是 `0x7FFFFFFF`,若系統是補 1,-1 右移 1 後會是 `0xFFFFFFFF`,所以 `logical` 是 1 若系統是位移是補 0,反之 `logical` 是 0 系統是補 1
* 這裡期望負數的話系統補 1 ,在這情況下因為 `logical` 為 0, `fixu` 和 `fix` 皆為 0,所以 `return (m >> n)` 就會輸出預期的結果
* 若系統負數的位移是補 0 的話,我們需要 `fix` 這個 mask 來做修正
* `m` 為負數的話,我們會希望 mask 的格式是 `1...10...0`,1 的個數是位移的個數,所以 OP2 = `m < 0`,紀錄 m 是正數還是負數,如此 `(fix ^ (fix >> n))` 可以得出我們上面要的 mask
## 測驗2
__builtin_ctz 來實作 [LeetCode 342. Power of Four](https://leetcode.com/problems/power-of-four/)
```c=
bool isPowerOfFour(int num)
{
return num > 0 && (num & (num - 1))==0 &&
!(__builtin_ctz(num) OPQ);
}
```
`OPQ = & 0x1`
* ctz 和 clz 是 [GCC extension](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) 所提供
> 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.
>
>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.
一個數 `num` 為 4 的冪次方,必須滿足
* num > 0
* num 必須為 2 的冪次方,因為 $4^n$ = $2^{2n}$,二進制形式為 `0...010...0`,所以 `num & num - 1 = 0`
* 因為 $4^n$ = $2^{2n}$,`num` 只可能是 2 的 0,2,4... 偶數次,所以從 LSB 開始數的 0 的個數為偶數個,`__builtin_ctz(num) & 1` 可以檢查是否為偶數
* LeetCode 練習
[1009. Complement of Base 10 Integer](https://leetcode.com/problems/complement-of-base-10-integer/)
```c=
class Solution {
public:
int bitwiseComplement(int N) {
return N ? N ^ (0xffffffff >> __builtin_clz(N)) : 1;
}
};
```
[41. First Missing Positive](https://leetcode.com/problems/first-missing-positive/)
```c=
class Solution {
public:
void swap(int &a, int &b) {
int t = a;
a = b;
b = t;
}
int firstMissingPositive(vector<int>& nums) {
int numSize = nums.size();
for (int i = 0; i < numSize; ++i) {
if (nums[i] > 0 && nums[i] < numSize && nums[i] != nums[nums[i] - 1]) {
swap (nums[i], nums[nums[i] - 1]);
--i;
}
}
for (int i = 0; i < numSize; ++i) {
if (nums[i] != i + 1)
return i + 1;
}
return numSize + 1;
}
};
```
> 不太明白哪裡可以使用到 cltz
## 測驗3
利用 GCC builtin func 實作 LeetCode [1342. Number of Steps to Reduce a Number to Zero](https://leetcode.com/problems/number-of-steps-to-reduce-a-number-to-zero/)
```c=
int numberOfSteps (int num)
{
return num ? AAA + 31 - BBB : 0;
}
```
`AAA = __builtin_popcount(num)`, `BBB = __builtin_clz(num)`
思路 : 二進制表示法中,每一個 `1` 代表至少需要做 1 次(奇數減1的行為),接著需要把最靠近 MSB 的 `1` 移到 LSB,所需的步數是 `31 - __builtin_clz(num) `
> TODO : 研讀 `unsigned popcnt_branchless(unsigned v)` 數學原理
## 測驗4
運用 bitwise operation 完成 GCD 的實作
```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;
}
```
`XXX = v`, `YYY = u << shift`
程式運作原理:
```c=5
for (shift = 0; !((u | v) & 1); shift++) {
u /= 2, v /= 2;
}
```
若 `u` 和 `v` 皆為偶數的話,LSB 必為 0 ,`& 1` 後取 not 可以判斷兩數是否都是偶數,是的話可以把 2 提出來,這樣只要計算 gcd(u/2 , v/2) 就好,迭代運算下去直到其中一數變成奇數,並用 `shift` 計算提了幾個 2 ,最後再乘回去
```c=8
while (!(u & 1))
u /= 2;
```
若 `u` 是是偶數,則可以把 `u` 的 2 的因數全數提出,因為 `u` , `v`必定不會有公因數 2,
```c=10
do {
while (!(v & 1))
v /= 2;
if (u < v) {
v -= u;
} else {
uint64_t t = u - v;
u = v;
v = t;
}
} while (XXX);
```
第 11 行 while loop 原理同上述,將 `v` 的 2 的因數全部提出,但是下面會做兩數的相減,有可能奇數會變成偶數,所以必須放在 do while loop 內每一輪都要檢查。剩下的部分是在做輾轉相除法,並確保每一輪都是大的數 - 小的數,且進入下一輪的 `v` 總是大數減小數的結果,若 `u == v`,`v` 會等於 0 且結束計算。
```c=21
return YYY; // YYY = u << shift
```
最後 `u` 必須左移修正一開始提出的公因數 2 的個數
## 測驗5
將 bit array 程式碼利用 clz 改寫
```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;
}
```
`KKK = bitset & -bitset`
> TODO,暫無想法