# 2020q3 Homework6 (quiz6)
contributed by < `nelsonlai1` >
## 測驗 1
![](https://i.imgur.com/FCJV0On.png)
浮點數結構分為 sign, exponent, fraction 三個部分,而浮點數的值為 $2^{(exponent-127)} \times (1 + fraction)$,且 sign 決定正負號,其中 fraction 的計算是最左邊的 bit 為 $2^{-1}$,第 2 個為 $2^{-2}$,以此類推。
```c=
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 &= 0xff800000;
r /= 256;
y = x + r;
*py &= 0xffff0000;
return y;
}
```
執行結果 :
```
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
```
這題可以先從執行結果看起,可以看到會將原本(十六進位)的第 5 個位數做"七捨八入",再把後面的位數捨去,換成二進位來說,是將第 17 位元加 1 後捨去 17 和後面的位元。
前面 5 ~ 10 行在判斷輸入的 x 是否為 0 或無限或 NAN,是的話直接返回原值,因為轉換後不影響值。
第 15 行 `*pr &= 0xff800000;` 是為了取出 sign bit 和 exponent bits。
第 16,17 行用例子比較好解釋,可以先將原本的 x 當作 $1.xx \times 2^k$ (負數則是 $1.xx \times -2^k$),
`*pr &= 0xff800000;` 後 r 變成 $2^k$ (x 為負數時 $-2^k$),
`r /= 256` 後 r 變成 $2^{k-8}$ ($-2^{k-8}$),
`y = x + r` 的 y 為 $1.xx \times 2^k + 2^{k-8} = 2^k \times (1.xx + 2^{-8})$ ,相當於原本的 x 的第 17 位元加 1。
(x 為負數時則是 $1.xx \times -2^k + (-2^{k-8}) = -2^k \times (1.xx + 2^{-8})$,一樣成立)
`*py &= 0xffff0000;` 則是將後面 16 個位元捨去,如此一來就達到預想的輸出結果了。
## 測驗 2
```c=
#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)
```
這題用了一個大小為 size + 1 的 array 做為 ring buffer。
可以先從 `ringbuf_write` 看起,可以看到 `ringbuf_write` 會先在 end 的地方寫入值,再做 `ingbuf_write_skip`,因此可以推測 `ringbuf_write_skip` 是在把 end 的位置更新。到這裡為止,可以知道 `NEXT_END_INDEX` 是在把 end 的位置往後推一位,而如果到 array 尾端的話,就把 end 改到 0,所以 RB2 = 1。`ringbuf_read` 的部分大致相同,所以也可以推斷出 RB1 = 1。
而 `ringbuf_write_skip` 中除了做 `(BUF)->end = NEXT_END_INDEX(BUF);` 外,還有判斷 `is_ringbuf_empty` (end == start),如果成立的話,`(BUF)->start = NEXT_START_INDEX(BUF);`。
這個部分也是為什麼要用 size + 1 的 array 的原因。假設 start 現在是 0,end 是 size,也就是 array 中有 size 個元素,如果這時要再寫入的話,end 會移到 0,觸發 start 往後移一位。因為讀取是從 start 開始讀,所以即使 array 中現在有 size + 1 個元素了,ring buffer 還是只有 size 個元素。
假設 ring buffer size = 7,使用者原本輸入 1234567
```graphviz
digraph {
start [shape = plaintext]
end [shape = plaintext]
rb [label = "<0>1|2|3|4|5|6|7|<8>" shape = record]
start -> rb:<0>
end -> rb:<8>
}
```
再寫入 8 :
```graphviz
digraph {
start [shape = plaintext]
end [shape = plaintext]
rb [label = "<0>1|<1>2|3|4|5|6|7|<8>8" shape = record]
start -> rb:<0>
end -> rb:<8>
}
```
```graphviz
digraph {
start [shape = plaintext]
end [shape = plaintext]
rb [label = "<0>1|<1>2|3|4|5|6|7|8" shape = record]
start -> rb:<0>
end -> rb:<0>
}
```
```graphviz
digraph {
start [shape = plaintext]
end [shape = plaintext]
rb [label = "<0>1|<1>2|3|4|5|6|7|8" shape = record]
start -> rb:<1>
end -> rb:<0>
}
```
## 測驗 3
```c=
#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;
}
```
這題在實作一個 singly-linked list 的 insertion sort。`sorted_insert` 這個 function 將 node 依照 increasing order 插入 list 中(可以從執行結果看出),根據第一周的訓練,
可以得到 `SS1 = node->next = *head`, `SS2 = *head = node`,這裡的判斷是如果 head 不存在或是 node value <= head value 時,將 node 插到第一個位置,並將 head 改為 node。而 20 ~ 23 行則是依照 insertion sort 的方法,將 node 插到正確的位置。
`sort` 這個 function 是先建立一個叫做 sorted 的空白 list,並將要做 sorting 的 list 的元素一個個用 `sorted_insert` 插入到 sorted 中,這樣最後 sorted 就會是以排序好的狀態。
A, B, C, D 的值可以參考 [C 語言程式設計技巧](https://hackmd.io/@sysprog/c-trick#inode) 裡的靜態 linked list。需要注意的是這裡是
```c=
#define cons(x, y) (struct llist[]){{y, x}}
struct llist {
int val;
struct llist *next;
};
```
x 是 `*next` 而 y 是 val,所以 head 是最外圍的 D,並依序往內。因此 A = 7, B = 4, C = 5, D = 9。
## 測驗 4
給定一個整數序列,其中會有一個數值重複,請找出。
已知條件:
1. 假設陣列長度為 $n$,數值的範圍是 $1$ 到 $n−1$
2. 重複的數值不一定只重複一次
```c=
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;
}
```
第四行 `const size_t log_2 = 8 * sizeof(int) - __builtin_clz(numsSize);` 其實等於
`32 - __builtin_clz(numsSize)`,所以是在求 numsSize 的 MSB,因為數值範圍是 1 到 numsSize - 1,所以這裡其實可以用 `__builtin_clz(numsSize - 1)`,不過影響不大。
接著第一個 for loop 在做的是對逐個 bit 做檢查,c1 紀錄的是從 0 到 numsSize - 1 總共在該 bit 有幾個 1-bits,c2 則是給定的陣列裡面總共在該 bit 有幾個 1-bits。可以想見的是,如果重複的數在該 bit 是 0,c1 會大於等於 c2 (因為重複的數出現意味著壓縮其他在該 bit 為 1 的數字出現的機會),而如果重複的數在該 bit 是 1,c1 會小於 c2。
舉例來說,如果今天數值範圍是 1 ~ 3,c1 會記錄 0 ~ 3 在各個 bit 上有幾個 1,在最後一位上有 2 個 1,在倒數第二位上有 2 個 1。c2 紀錄 nums[0] ~ nums[3] 在各個 bit 上有幾個 1。如果現在重複的是 1,可能是 [1,1,2,3], [1,1,1,2], [1,1,1,3], [1,1,1,1] (順序不計),在最後一位時,c2 分別是 3, 3, 4, 4,倒數第二位時,c2 分別是 2, 1, 1, 0。所以可以得到重複的數最後一位是 1,倒數第二位是 0。
因為第 14 行 CCC 成立時 `res += bit;`,所以可以知道 `CCC = c1 < c2`
### 延伸問題
#### 實作不同的程式碼 (合法的 C 程式,可運用 GNU extension),使其表現優於上述程式碼
```c=
int findDuplicate(int *nums, int numsSize)
{
int bk[numsSize];
memset(bk, 0, sizeof(bk));
for (int i = 0; i < numsSize; i++) {
if (nums[i] == bk[nums[i]])
return nums[i];
bk[nums[i]] = nums[i];
}
return 0;
}
```
蠻直觀的作法,運用 bucket sort 的概念,一開始先設一個全部數值皆為 0 的 array,如果檢查到nums 中有 nums[i] == bk[nums[i]],代表該數重複了,所以 return nums[i]。反之將
bk[nums[i]] = nums[i],這樣下次重複時就能發現。
這樣做的好處是當有重複數字出現時會馬上結束,壞處是需要額外 O(n) 空間。另外因為 leetcode 不會將上次結果清除,所以第四行的 memset 一定要做。
![](https://i.imgur.com/ulYDgRw.png)