# 2020q3 Homework6 (quiz6)
contributed by < `zzzxxx00019` >
>[2020q3 第 6 週測驗題](https://hackmd.io/@sysprog/2020-quiz6)
## 測驗 1
### 題目:
![](https://i.imgur.com/hNeEBIk.png)
BFloat16 的範例
* 4049Hex = 0 10000000 1001001 = 3.14062510 ≈ π
下列是個轉換程式:
```cpp=
float fp32tobf16(float x) {
float y = x;
int *py = (int *) &y;
unsigned int exp, man;
exp = *py & 0x7F800000u;
man = *py & 0x007FFFFFu;
if (!exp && !man) /* zero */
return x;
if (exp == 0x7F800000u) /* infinity or NaN */
return x;
/* Normalized number. round to nearest */
float r = x;
int *pr = (int *) &r;
*pr &= BB1;
r /= 256;
y = x + r;
*py &= BB2;
return y;
}
```
對應的測試程式:
```cpp
void print_hex(float x) {
int *p = (int *) &x;
printf("%f=%x\n", x, *p);
}
int main() {
float a[] = {3.140625, 1.2, 2.31, 3.46, 5.63};
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
print_hex(a[i]);
float bf_a = fp32tobf16(a[i]);
print_hex(bf_a);
}
return 0;
}
```
參考執行結果:
```
3.140625=40490000
3.140625=40490000
1.200000=3f99999a
1.203125=3f9a0000
2.310000=4013d70a
2.312500=40140000
3.460000=405d70a4
3.453125=405d0000
5.630000=40b428f6
5.625000=40b40000
```
請補完程式碼。
### 原理:
```cpp=2
float y = x;
int *py = (int *) &y;
unsigned int exp, man;
exp = *py & 0x7F800000u;
man = *py & 0x007FFFFFu;
```
* 將浮點數 `x` 以 `int` 方式,表達每個 `bit` 的數值
* 透過對 `0x7F800000u` 做 `and` 運算,可以得到 `fraction` 的 `bit`
* 透過對 `0x007FFFFFu` 做 `and` 運算,可以得到 `exponent` 的 `bit`
```cpp=7
if (!exp && !man) /* zero */
return x;
if (exp == 0x7F800000u) /* infinity or NaN */
return x;
```
* 預先檢查不同 `case` ,做出不同的配套措施, `zero` 、 `infinity` 、 `NaN` 都直接回傳 `x`
|value|sign|exponent|fraction|
| - | - | - | - |
| 0 | |00000000|00..00 |
| inf | |11111111|00..00 |
| NaN | |11111111| !=0 |
```cpp=12
/* Normalized number. round to nearest */
float r = x;
int *pr = (int *) &r;
*pr &= BB1;
r /= 256;
y = x + r;
```
* 利用 `*pr &= 0xff800000` 取出 `sign` 與 `exp` 的部分
* 原本的 $x$ 可表達為 $2^k\times1.fraction$ ,經過 `15` 、 `16` 行的運算後, $r=2^{k-8}$
* $y=x+r$ 就可寫成 $2^k\times1.fracion+2^{k-8}$ ,整理後得到 $2^k\times(1.fraction+2^{-8})$ ,相當於對 `fraction` 的部分,在第 `8 bit` 加上 `1` ,意即對分數的第 `8 bit` 無條件進位
```cpp=19
*py &= BB2
```
* 最後再保留下 `bfloat16` 的 `sign` 、 `exp` 、 `fraction` ,透過 `*py &= 0xffff0000` 達到此目的
---
## 測驗 2
### 題目:
考慮以下 [ring buffer](https://en.wikipedia.org/wiki/Circular_buffer) 的實作:
```cpp
#define RINGBUF_DECL(T, NAME) \
typedef struct { \
int size; \
int start, end; \
T *elements; \
} NAME
#define RINGBUF_INIT(BUF, S, T) \
{ \
static T static_ringbuf_mem[S + 1]; \
BUF.elements = static_ringbuf_mem; \
} \
BUF.size = S; \
BUF.start = 0; \
BUF.end = 0;
#define NEXT_START_INDEX(BUF) \
(((BUF)->start != (BUF)->size) ? ((BUF)->start + RB1) : 0)
#define NEXT_END_INDEX(BUF) (((BUF)->end != (BUF)->size) ? ((BUF)->end + RB2) : 0)
#define is_ringbuf_empty(BUF) ((BUF)->end == (BUF)->start)
#define is_ringbuf_full(BUF) (NEXT_END_INDEX(BUF) == (BUF)->start)
#define ringbuf_write_peek(BUF) (BUF)->elements[(BUF)->end]
#define ringbuf_write_skip(BUF) \
do { \
(BUF)->end = NEXT_END_INDEX(BUF); \
if (is_ringbuf_empty(BUF)) \
(BUF)->start = NEXT_START_INDEX(BUF); \
} while (0)
#define ringbuf_read_peek(BUF) (BUF)->elements[(BUF)->start]
#define ringbuf_read_skip(BUF) (BUF)->start = NEXT_START_INDEX(BUF);
#define ringbuf_write(BUF, ELEMENT) \
do { \
ringbuf_write_peek(BUF) = ELEMENT; \
ringbuf_write_skip(BUF); \
} while (0)
#define ringbuf_read(BUF, ELEMENT) \
do { \
ELEMENT = ringbuf_read_peek(BUF); \
ringbuf_read_skip(BUF); \
} while (0)
```
其測試程式如下:
```cpp
#include <assert.h>
RINGBUF_DECL(int, int_buf);
int main()
{
int_buf my_buf;
RINGBUF_INIT(my_buf, 2, int);
assert(is_ringbuf_empty(&my_buf));
ringbuf_write(&my_buf, 37);
ringbuf_write(&my_buf, 72);
assert(!is_ringbuf_empty(&my_buf));
int first;
ringbuf_read(&my_buf, first);
assert(first == 37);
int second;
ringbuf_read(&my_buf, second);
assert(second == 72);
return 0;
}
```
請補完程式碼,使得測試程式得以正確執行。
### `do { ... } while(0)` 巨集:
參考 [multi-line macro: do/while(0) vs scope block](https://stackoverflow.com/questions/1067226/c-multi-line-macro-do-while0-vs-scope-block) 中討論區的解釋
#### 比較以下兩種不同方法的差別:
* 第一種,使用 `do/while(0) loop`
```cpp
#define FOO \
do { \
do_stuff_here \
do_more_stuff \
} while (0)
```
* 第二種,使用 `basic block`
```cpp
#define FOO \
{ \
do_stuff_here \
do_more_stuff \
}
```
#### 解釋差別:
* 考慮到以下的 `code`
```cpp
if (<condition>)
foo(a);
else
bar(a);
```
* 若將 `foo(a)` 以 `macro` 的方式取代
```cpp
if (<condition>)
CALL_FUNCS(a);
else
bar(a);
```
如果使用的巨集是 `basic block` ,會導致 `compile` 失敗,原因是當執行完 `if` 的 `true branch` 後,碰上執行完 `macro` 後的分號 「 ; 」 ,導致判斷式終結,然而 `else` 無法單獨存在,而出現 `compile error` ,而要修正此問題的方法,就是呼叫 macro 後,後面別加上分號,修正後如下:
```cpp
if (<condition>)
CALL_FUNCS(a)
else
bar(a);
```
但這種方法顯得比較彆扭,比較理想的方法是確保 `macro` 可以展開為 `regular statement` ,而非上面的 `compound statement` ,因此透過 `do{ ... }while(0)` 這樣的寫法,可以維持原本 `coding` 的習慣,不會出現 `compile error` ,在使用 `macro` 看起來也較一致,注意到的是 `...while(0)` 後面不可加上分號,否則就會出現像上述那樣的 `compound statement` 問題
* 將 `macro` 以 `do/while(0) loop` 的方式包覆內容程式
```cpp
#define CALL_FUNCS(x) \
do { \
func1(x); \
func2(x); \
func3(x); \
} while (0)
```
在條件式使用 `macro` 就可以不用刻意去注意分號的問題
```cpp
if (<condition>)
CALL_FUNCS(a);
else
bar(a);
```
### 原理:
* `Ring Buffer` 實作一個環形的 `buffer` ,透過下圖解釋其結構:
初始狀態,假設給定 `buffer size` 為 `6` 的 `ring buffer` , `start` 與 `end` 都指向第 `0` 格的位置
```graphviz
digraph {
start [shape = plaintext]
end [shape = plaintext]
ring_buffer [label = "<0> | | | | |" shape=record]
start -> ring_buffer:0
end -> ring_buffer:0
}
```
每次新增資料時,都寫入到 `end` 的位置,而 `end` 再往後退一格,例如將 `1` 寫入, `end` 則從第一格退向第二格
```graphviz
digraph {
start [shape = plaintext]
end [shape = plaintext]
ring_buffer [label = "<0> 1|<1> | | | |" shape=record]
start -> ring_buffer:0
end -> ring_buffer:1
}
```
當 `buffer` 飽和的狀態下,會出現 `start` 與 `end` 同時都指向同一格的狀況
```graphviz
digraph {
start [shape = plaintext]
end [shape = plaintext]
ring_buffer [label = "<0>1|2|3|4|5|6" shape=record]
start -> ring_buffer:0
end -> ring_buffer:0
}
```
為了解決 `buffer` 在飽和狀態,在 end 寫入資料,會導致 `end` 退到 `start` 後面的狀況,因此預先將 `start` 往後退一格
```graphviz
digraph {
start [shape = plaintext]
end [shape = plaintext]
ring_buffer [label = "<0>1|<1>2|3|4|5|6" shape=record]
start -> ring_buffer:1
end -> ring_buffer:0
}
```
因此當下一筆資料寫入飽和的 `ring buffer` 時,將會捨去 `ring` 中存放最久的 `element` ,以寫入 `7` 為例,將原本的 `1` 給取代,因又再次飽和, `start` 往後退一格
```graphviz
digraph {
start [shape = plaintext]
end [shape = plaintext]
ring_buffer [label = "<0>7|<1>2|<2>3|4|5|6" shape=record]
start -> ring_buffer:2
end -> ring_buffer:1
}
```
而取出資料時,從 `start` 開始讀出,並將 `start` 往後退一格
```graphviz
digraph {
start [shape = plaintext]
end [shape = plaintext]
ring_buffer [label = "<0>7|<1>2|<2>|<3>4|5|6" shape=record]
start -> ring_buffer:3
end -> ring_buffer:1
}
```
* 再從 `ringbuf_write` 與 `ringbuf_read` 兩個角度去看 `ring` 的整個實作流程:
#### `ringbuf_write` 程式碼
```cpp
#define ringbuf_write(BUF, ELEMENT) \
do { \
ringbuf_write_peek(BUF) = ELEMENT; \
ringbuf_write_skip(BUF); \
} while (0)
```
* 呼叫 `ringbuf_write_peek()` ,將 `ELEMENT` 寫入 `BUF` 的 `end`
```cpp
#define ringbuf_write_peek(BUF) (BUF)->elements[(BUF)->end]
```
* 新增 `ELEMENT` 後,呼叫 `ringbuf_write_skip()` ,調整當前 `end` 的位置
* `NEXT_END_INDEX()` : 調整 `(BUF)->end` 位置,如果 `end` 位置尚未走到 `buf` 的 `size` ,代表 `end` 仍有向後退的空間,故 `end` 向後退 `1` 格,即 `(BUF)->end + 1` ,若 `end` 已走到 `buffer` 的最尾端,則回到起始點 `0` 的地方,如此達到 `ring` 的效果
* `is_ringbuf_empty(BUF)` : 原本是檢查 `buffer` 是否為 `empty` ,但若在新增完 `element` 後, `end` 與 `start` 同一位置,代表 `buffer` 目前為飽和的狀態,則必須對目前 `(BUF)->start` 做相對應的處理
* `NEXT_START_INDEX()` : 調整 `(BUF)->start` 位置,如果 `start` 位置尚未走到 `buf` 的 `size` ,代表 `start` 仍有向後退的空間, 故 `(BUF)->start + 1` ,若 `start` 已走到尾端,則回到 `BUF` 的初始位置 `0`
* 呼叫 `NEXT_START_INDEX()` ,處理 `buffer` 在飽和狀態時, `start` 與 `end` 都指向起始位置的問題,將 start 往後退一格,即捨去掉 `buffer` 中,存放最久的 `element`
```cpp
#define NEXT_END_INDEX(BUF) (((BUF)->end != (BUF)->size) ? ((BUF)->end + RB2) : 0)
```
```cpp
#define NEXT_START_INDEX(BUF) \
(((BUF)->start != (BUF)->size) ? ((BUF)->start + RB1) : 0)
```
```cpp
#define is_ringbuf_empty(BUF) ((BUF)->end == (BUF)->start)
```
```cpp
#define ringbuf_write_skip(BUF) \
do { \
(BUF)->end = NEXT_END_INDEX(BUF); \
if (is_ringbuf_empty(BUF)) \
(BUF)->start = NEXT_START_INDEX(BUF); \
} while (0)
```
#### `ringbuf_read` 程式碼
```cpp
#define ringbuf_read(BUF, ELEMENT) \
do { \
ELEMENT = ringbuf_read_peek(BUF); \
ringbuf_read_skip(BUF); \
} while (0)
```
* 呼叫 `ringbuf_read_peek()` ,將 `ring buffer` 中, `start` 指向的 `element` 取出
```cpp
#define ringbuf_read_peek(BUF) (BUF)->elements[(BUF)->start]
```
* 再透過 `ringbuf_read_skip()` ,調整取出 `buffer` 元件後, `start` 的位置
```cpp
#define ringbuf_read_skip(BUF) (BUF)->start = NEXT_START_INDEX(BUF);
```
---
## 測驗 3
### 題目:
考慮到以下靜態初始化的 singly-linked list 實作:
```cpp=
#include <stdio.h>
/* clang-format off */
#define cons(x, y) (struct llist[]){{y, x}}
/* clang-format on */
struct llist {
int val;
struct llist *next;
};
void sorted_insert(struct llist **head, struct llist *node)
{
if (!*head || (*head)->val >= node->val) {
SS1;
SS2;
return;
}
struct llist *current = *head;
while (current->next && current->next->val < node->val)
current = current->next;
node->next = current->next;
current->next = node;
}
void sort(struct llist **head)
{
struct llist *sorted = NULL;
for (struct llist *current = *head; current;) {
struct llist *next = current->next;
sorted_insert(&sorted, current);
current = next;
}
*head = sorted;
}
int main()
{
struct llist *list = cons(cons(cons(cons(NULL, A), B), C), D);
struct llist *p;
for (p = list; p; p = p->next)
printf("%d", p->val);
printf("\n");
sort(&list);
for (p = list; p; p = p->next)
printf("%d", p->val);
printf("\n");
return 0;
}
```
其執行結果為:
```
9547
4579
```
請補完程式碼,使程式碼和執行結果相匹配。
### 原理:
```cpp=4
#define cons(x, y) (struct llist[]){{y, x}}
```
* 參考 [你所不知道的C語言:技巧篇](https://hackmd.io/@sysprog/c-trick#inode) 文章內容
* 使用 `con(x, y)` 可以得到 `node->val = y` 且 `node->next = x` 的節點
* 因此將 `cons(cons(cons(cons(NULL, A), B), C), D)` 展開可以得到以下的 `List`
```graphviz
digraph
{
rankdir=LR ;
node [shape = record] ;
D->C->B->A->NULL
}
```
```cpp=12
void sorted_insert(struct llist **head, struct llist *node)
{
if (!*head || (*head)->val >= node->val) {
SS1;
SS2;
return;
}
struct llist *current = *head;
while (current->next && current->next->val < node->val)
current = current->next;
node->next = current->next;
current->next = node;
}
```
* `Linked list` 經排列後,將 `node` 插入到相對應的位置
* 透過 `pointer-to-pointer` 直接對 `main` 的 `head` 取值,因此 `**head` 為 `main head` 實際的位址
* 當 `list` 沒有 `node` 或是 `head->value >= node->value` 時, `head` 將由 `node` 取代
* `SS1 : node->next = *head` ,將 `node` 的 `next` 接上原本的 `head`
* `SS2 : *head = node` ,再將 `node` 設為目前的 `head`
* 否則依序尋訪節點,找到適當的位置,將新的節點插入
```cpp=26
void sort(struct llist **head)
{
struct llist *sorted = NULL;
for (struct llist *current = *head; current;) {
struct llist *next = current->next;
sorted_insert(&sorted, current);
current = next;
}
*head = sorted;
}
```
* `sort` 的方法是依序尋訪每個節點,並透過 `sorted_insert` 建立一個 `sort list`
* 當 `current != NULL` ,就將 `current node` 透過 `sorted_insert` 加入 `sorted list`
* 最後再將 `head` 接上排序好的 `sorted list`
```cpp=39
struct llist *list = cons(cons(cons(cons(NULL, A), B), C), D);
```
* 若要使 `list` 以 `9547` 的順序排列,則 `A ~ D` 應依序填入 `7` 、 `4` 、 `5` 、 `9`
---
## 測驗 4
### 題目:
LeetCode [287. Find the Duplicate Number](https://leetcode.com/problems/find-the-duplicate-number/)
```cpp
int findDuplicate(int *nums, int numsSize)
{
int res = 0;
const size_t log_2 = 8 * sizeof(int) - __builtin_clz(numsSize);
for (size_t i = 0; i < log_2; i++) {
int bit = 1 << i;
int c1 = 0, c2 = 0;
for (size_t k = 0; k < numsSize; k++) {
if (k & bit)
++c1;
if (nums[k] & bit)
++c2;
}
if (CCC)
res += bit;
}
return res;
}
```
```
Example 1:
Input: nums = [1,3,4,2,2]
Output: 2
Example 2:
Input: nums = [3,1,3,4,2]
Output: 3
Example 3:
Input: nums = [1,1]
Output: 1
```
### 原理:
* 題目測資有一個很重要的規定:陣列長度為 $n$ ,數值的範圍是 $1$ 到 $n-1$ ,因此陣列中,不會有數值為 `0` 的測資出現,即重複的值必定取代 `0` 而出現 `1` 次以上
* 若把陣列中,每個 `bit` 拆開比對,取代 `0` 的就是重複出現的數字,因此在只重複出現一次的可能時,重複出現的那個數字在該值 `bit` 為 `1` 的位置時,會比 `0` 多出 `1 bit`
* 考慮到重複出現的次數不只 `1` 次,代表重複出現的數字,在該值 `bit 1` 的位置,其 `bit 1` 的數量必定大於原本數值為 `0 ~ n-1` 的陣列,因此 `CCC=c1<c2`
假設 `numsSize = 3:` , `c1` 在計算下表中,每個 `bit` 為 `1` 的數量
| val | bit 1 | bit 0 |
| -- | -- | -- |
| 0 | 0 | 0 |
| 1 | 0 | 1 |
| 2 | 1 | 0 |
|bit 1| 1 | 1 |
若 `nums = [1,1,2]:` , `c2` 在計算下表中,每個 `bit` 為 `1` 的數量
| val | bit 1 | bit 0 |
| -- | -- | -- |
| 1 | 0 | 1 |
| 1 | 0 | 1 |
| 2 | 1 | 0 |
|bit 1| 1 | 2 |
因為下表的 `1` 取代了上表中的 `0` ,因此在下表在 `bit 0` 的位置,數量會大於上表,透過此方法找出每個 `bit` 多出來的數值,就可以知道哪個數字重複,此方法在 leetcode 上的表現結果如下:
![](https://i.imgur.com/PSiJ1e1.png)
### 延伸問題:
* 實作不同的程式碼
* 以桶子排序法的方式,透過 `bucket[i]` 計算 `i` 值出現的次數
* 當 `num[i] > 1` ,代表 `i` 即為重複的值
* 也可用 `bool` 對 `bucket[i]` 中每個位置設立 `flag` ,當 `flag = 1` 且有值填入,即重複
```cpp=
int findDuplicate(int *nums, int numsSize) {
int bucket[numsSize] ;
memset(bucket, 0, sizeof(bucket)) ;
for (int i=0 ; i<numsSize ; i++){
bucket[ nums[i] ] += 1 ;
if ( bucket[ nums[i] ] > 1 ) return nums[i] ;
}
return 0 ;
}
```
![](https://i.imgur.com/1cjsJUo.png)
```cpp=
int findDuplicate(int *nums, int numsSize) {
bool bucket[numsSize] ;
memset(bucket, 0, sizeof(bucket)) ;
for (int i=0 ; i<numsSize ; i++){
if ( !bucket[ nums[i] ] ) bucket[ nums[i] ] ^= 1 ;
else return nums[i] ;
}
return 0 ;
}
```
![](https://i.imgur.com/Y4o9Qj2.png)