# 2022q1 Homework2 (quiz2)
contributed by <`HScallop`>
###### tags: `linux2022`
> [2022q1 quiz2](https://hackmd.io/@sysprog/linux2022-quiz2)
### 測驗 1
```c
#include <stdint.h>
uint32_t average(uint32_t a, uint32_t b)
{
return (a >> 1) + (b >> 1) + (EXP1);
}
```
EXP1 = ?
> a & b & 1
```c
#include <stdint.h>
uint32_t average(uint32_t a, uint32_t b)
{
return (EXP2) + ((EXP3) >> 1);
}
```
EXP2 = ?
> a & b
EXP3 = ?
> a ^ b
#### 延伸問題
1. 當初這兩題在上課有回答出來。思考這兩題的時候,覺得很類似於以前上邏輯設計的時候 full-adder 的作法。
在 EXP1 的程式碼因為先各自 ~~>> 1~~ `/2` ,所以除了最小位數外,不需要再考慮 propagation ,而 EXP2, EXP3 就像是經典的 full-adder 加法後,再 ~~>> 1~~ `/2` 的結果。
![](https://i.imgur.com/m64J3ve.png)
從上圖可以看出 sum 就是 a ^ b propagation 則是 a & b 。
2.
---
### 測驗 2
```c
#include <stdint.h>
uint32_t max(uint32_t a, uint32_t b)
{
return a ^ ((EXP4) & -(EXP5));
}
```
EXP4 = ?
> a ^ b
EXP5 = ?
> a < b
#### 延伸問題
1. 可以參考 [解讀計算機編碼 - 不需要分支的設計](https://hackmd.io/@sysprog/binary-representation#%E4%B8%8D%E9%9C%80%E8%A6%81%E5%88%86%E6%94%AF%E7%9A%84%E8%A8%AD%E8%A8%88)想法上是利用 `diff = a - b` 還有 ~~(diff >> 31)~~ 判斷 `diff < 0` ,如果 `diff < 0` `b` 就會被加上 ``(a - b)`` 轉換成 `a` 。
回到測驗本測驗的問題,還是一樣要在 `b > a` 時,把 `a` 轉換成 `b` 。跟前面不一樣的地方是這邊想用 `XOR` 來實作。
所以推測在 `b > a` 時,會用 `a ^ a ^ b` 把 `a` 變成 `b` 。
2.
---
### 測驗 3
```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 (COND);
return RET;
}
```
COND = ?
> v
RET = ?
> u << shift
#### 延伸問題
1. 程式碼實現了題目給定的簡化規則,再用輾轉相除法找到 GCD,以下按照敘述來對應程式碼行數
- If both x and y are 0, gcd is zero $gcd(0, 0)$.
- $gcd(x, 0) = x$ and $gcd(0, y) = y$ because everything divides 0.
> line 4
> `u` 或 `v` 會有一個是 `0` 所以 `u | v` 會是非 `0` 的另外一個數。
- If both x and y are even, $gcd(x, y) = 2 * gcd(\dfrac{x}{2}, \dfrac{y}{2})$ because 2 is a common divisor.
Multiplication with $2$ can be done with bitwise shift operator.
> line 6-8
> `!((u | v) & 1)` 這邊的 `AND 1` 就是最小一位數的 mask 只要 `u` 或 `v` 有一個是奇數,就會跳出迴圈。
- If x is even and y is odd, $gcd(x, y) = gcd(\dfrac{x}{2}, y)$, vice versa.
> line 9-21
> while loop on line 9: u is even and v is odd.
> while loop on line 12: u is odd and v is even.
> line 11 開始的 do while 迴圈是輾轉相除法的實作,比較特別的是他會把相減之後的數留在 `v` ,繼續用 u is odd and v is even 的規則簡化。
> 所以這邊 `COND` 是輾轉相除法的終止條件。
> 而`RET` 是最後結果,需要注意剛剛照著規則提出的 2 要乘回來。
---
2.
### 測驗 4
```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;
}
```
改寫的程式碼:
```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 = EXP6;
int r = __builtin_ctzll(bitset);
out[pos++] = k * 64 + r;
bitset ^= t;
}
}
return pos;
}
```
EXP6 = ?
> bitset & -bitset
#### 延伸問題
1. 其實在原本的題目中就有說明第 9 行的作用是找出最小的位數的 `1` ,這邊利用了 2's complement 的特性。
e.g.
$010001\underbrace{0...0}_\text{n}$
2's complement:
1. invert bits $101110\underbrace{1...1}_\text{n}$
2. add 1 $101111\underbrace{0...0}_\text{n}$
比較可以發現,只會有最小一位的 `1` 和更小位數的 `0` 會一樣,其他位置都已經被反轉。
第一段程式碼第 8 行的迴圈被取代了,運用 `__builtin_ctzll`,不需要一位一位檢查。
2.