# 2022q1 第 2 週測驗題 contributed by < ```DokiDokiPB``` > <!-- [quiz2](https://hackmd.io/@sysprog/BybKYf5xc) --> ### 測驗 ```1``` ```c #include <stdint.h> uint32_t average(uint32_t a, uint32_t b) { return (a >> 1) + (b >> 1) + (EXP1); } ``` 取 EXP1 為 ```a & b & 1``` ```(a >> 1) + (b >> 1)``` 會無條件捨去二進位中最低位元的位數,但若 a 與 b 在最低位加法時進位,則```(a >> 1) + (b >> 1)``` 需再加上 1 ![image alt](https://i.imgur.com/eQpT5GI.png) 以數位邏輯電路中, 取得進位數值的運算為```(a & b)``` 表示當前下個 $carry_{in}$的數值,即上圖中的 $carry_{out}$ ```c uint32_t average(uint32_t a, uint32_t b) { return (EXP2) + ((EXP3) >> 1); } ``` 取 EXP2 為 ```a & b``` 取 EXP3 為 ```a ^ b``` > 在[你所不知道的 C 語言:數值系統篇](https://hackmd.io/@sysprog/c-numerics) 節錄 在數位邏輯中, ```a + b``` 可以用表示```(a ^ b) + ((a & b) << 1)``` ```(a + b) / 2``` 相當於 ```((a ^ b) + ((a & b) << 1)) >> 1``` = ```(a ^ b) >> 1 + (a & b)``` ### 測驗 ```2``` ```c #include <stdint.h> uint32_t max(uint32_t a, uint32_t b) { return a ^ ((EXP4) & -(EXP5)); } ``` 取 EXP4 為 ```a ^ b``` 取 EXP5 為 ```a <= b``` 若只單獨觀察 ```a ^ ...``` 形式,可以推導 - ```a <= b```,則回傳的形式應為:```a ^ a ^ b = b``` - ```a > b```,則回傳的形式應為:```a ^ 0 = a``` 若 ```a <= b``` 成立,表示 ```-(a <= b)```的數值為```-1```,在二的補數系統中,表示```0xFFFFFFFF```。最終取得```a ^ a ^ b = b``` 若 ```a <= b``` 不成立,表示 ```-(a <= b)```的數值為```0```,在二的補數系統中,表示```0x00000000```。最終取得```a ^ 0 = a``` <!-- 以上是在二的補數系統下才成立的函式,但若是一的補數系統呢? 可以參考 > 在[解讀計算機編碼](https://hackmd.io/@sysprog/binary-representation#%E7%AC%AC%E4%B8%89%E9%83%A8%E5%88%86%EF%BC%9A%E5%9C%A8%E8%B3%87%E8%A8%8A%E5%AE%89%E5%85%A8%E7%9A%84%E6%87%89%E7%94%A8)節錄 > 自 C99 提出 Fixed width integer types (定義於標頭檔 <stdint.h>) 並規範採用二補數,因此諸如 int16_t, int32_t, int64_t 等經由 typedef 定義 (因此會以 _t 結尾)、編譯器實作商提供的衍生型態,必定是二補數系統。 > > 參見 C 語言規格書 (7.18.1.1 Exact-width integer types, 1.) The typedef name 𝚒𝚗𝚝_𝑁𝚝 designates a signed integer type with width 𝑁 , no padding bits, and a two’s complement representation. Thus, int8_t denotes a signed integer type with a width of exactly 8 bits --> ### 測驗 ```3``` ```c #include <stdint.h> uint64_t gcd64(uint64_t u, uint64_t v) { if (!u || !v) return u | v; while (v) { uint64_t t = v; v = u % v; u = t; } return u; } ``` 其等價實作 ```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``` 直接運算一次: - 例如 $gcd(84, 13)$ $$ 84 = 13 * 6 + 6 \\ 13 = 6 * 2 + 1 \\ 6 = 1*6 + 0 $$ - 例如 $gcd(84, 12)$ $$ 84 = 12 * 7 + 0 $$ 對應測驗題中原本 $gcd$ 的程式碼, while loop 的中止條件為: ```v``` = ```0```,推導出等價實作的 while loop 的中止條件也為```v```,因此取 COND 為 ```v``` 在題目敘述中 > If x and y are both even, gcd(x, y)= 2 * gcd(x, y) because 2 is a common divisor. Multiplication with 2 can be done with bitwise shift operator. 因此 ```shift``` 的作用為預先將 2 公因數提出,最後再做 bitwise operation 回去。 在等價實作的程式碼中,都是對 2 預先做處理,其餘運算事實上與原本的 $gcd$ 實作相同。 #### 延伸問題 2 (在 x86_64 上透過 __builtin_ctz 改寫 GCD,分析對效能的提升) #### 延伸問題 3 (Linux 核心中也內建 GCD (而且還不只一種實作),例如 lib/math/gcd.c,請解釋其實作手法和探討在核心內的應用場景) > 節錄自 [lib/math/gcd.c](https://github.com/torvalds/linux/blob/master/lib/math/gcd.c) ```c= unsigned long gcd(unsigned long a, unsigned long b) { unsigned long r = a | b; if (!a || !b) return r; /* Isolate lsbit of r */ r &= -r; while (!(b & r)) b >>= 1; if (b == r) return r; for (;;) { while (!(a & r)) a >>= 1; if (a == r) return r; if (a == b) return a; if (a < b) swap(a, b); a -= b; a >>= 1; if (a & r) a += b; a >>= 1; } } ``` 在程式碼第 3 ~ 5 行中,```r``` 為 ```a``` 與 ```b``` 的 2 的冪共同因數,可得 $gcd(a,b) = r * \space ...$ > 在測驗 ```1``` 的延伸閱讀 [Introduction to Low Level Bit Hacks #7. Isolate the rightmost 1-bit.](https://catonmat.net/low-level-bit-hacks),可以得知在二的補數下, ```r &= -r``` 取得最低位元的 1 的位置 在程式碼第 6 行中,已知 ```r``` 為 ```a``` 與 ```b``` 最大 2 的共同因數,將 ```b``` 多餘的 2 的冪移除。同理在 for loop 中,也將 ```a``` 的多餘的 2 的冪移除。 因此在接下來的程式碼中,假設```a``` = $mr$ 與 ```b``` = $nr$,其中 ```m, n``` 為奇數, ```r``` 為 2 的冪 在程式碼第 26 ~ 27 行中, ``` a -= b``` 必為 ```r``` 的倍數,且 ```(m - n)```必為偶數,必定多出至少一個 2 的冪,可以 ```a >>= 1``` 在程式碼第 28 ~ 30 行中,可以簡單用例子說明:假設 ```a``` = $3r$, ```b``` = $3r$,若不執行```if(a & r) a += b```,會得到 ```a``` = $1.5r$,這樣 ```a``` 不為 ```r``` 的整數倍數。 已知 $gcd(a + b, b) = gcd(a, b)$,透過加回一個 ```b``` 不影響結果。 並且因為 ```a += b``` 必定產生新的 2 的冪,透過 ```a >>= 1``` 移除。 > 事實上移除第 28 ~ 30 行並不影響結果,在下一個 for loop 中, ```a``` 多餘的 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; } ``` <!-- > 根據 --> ### 測驗 ```5``` ```c #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "list.h" struct rem_node { int key; int index; struct list_head link; }; static int find(struct list_head *heads, int size, int key) { struct rem_node *node; int hash = key % size; list_for_each_entry (node, &heads[hash], link) { if (key == node->key) return node->index; } return -1; } char *fractionToDecimal(int numerator, int denominator) { int size = 1024; char *result = malloc(size); char *p = result; if (denominator == 0) { result[0] = '\0'; return result; } if (numerator == 0) { result[0] = '0'; result[1] = '\0'; return result; } /* using long long type make sure there has no integer overflow */ long long n = numerator; long long d = denominator; /* deal with negtive cases */ if (n < 0) n = -n; if (d < 0) d = -d; bool sign = (float) numerator / denominator >= 0; if (!sign) *p++ = '-'; long long remainder = n % d; long long division = n / d; sprintf(p, "%ld", division > 0 ? (long) division : (long) -division); if (remainder == 0) return result; p = result + strlen(result); *p++ = '.'; /* Using a map to record all of reminders and their position. * if the reminder appeared before, which means the repeated loop begin, */ char *decimal = malloc(size); memset(decimal, 0, size); char *q = decimal; size = 1333; struct list_head *heads = malloc(size * sizeof(*heads)); for (int i = 0; i < size; i++) INIT_LIST_HEAD(&heads[i]); for (int i = 0; remainder; i++) { int pos = find(heads, size, remainder); if (pos >= 0) { while (PPP > 0) *p++ = *decimal++; *p++ = '('; while (*decimal != '\0') *p++ = *decimal++; *p++ = ')'; *p = '\0'; return result; } struct rem_node *node = malloc(sizeof(*node)); node->key = remainder; node->index = i; MMM(&node->link, EEE); *q++ = (remainder * 10) / d + '0'; remainder = (remainder * 10) % d; } strcpy(p, decimal); return result; } ``` 取 PPP 為 ```pos--``` 取 MMM 為 ```list_add``` 取 EEE 為 ```&(heads + (remainder % size ))``` <!-- 透過與其它 [LeetCode 的回答 Golang 版本](https://leetcode.com/problems/fraction-to-recurring-decimal/discuss/1729970/Golang-Solution-or-Simple-and-Easy-to-understand),比對程式碼對照程式碼行為 --> LeetCode 題目敘述為: - 輸入兩個數字,```numerator```(分子)與```denominator```(分母),輸出它的小數。以這樣形式的輸入必為有理數,可能是循環小數或是有限小數。 - 題目保證輸出結果一定是 $10^4$ 長度內 在此可能實作中,先做一次整數除法後,得到 ```division```與```remainder``` - ```division```便是小數點前的整數部位 - ```remainder```便是小數點後的部位 每一次 for loop ,計算小數點後幾位的數字,並且計算```remainder```。而中止條件為: * 當 remainder 為 ```0```,表示此小數為有限小數 * 當 remainder 在 hash map 中有相同的數字,表示此小數為循環小數。依據題目要求,在開始循環的小數部位前加入```(```與```)``` 以 ``` 337 / 333 ``` 為例: ``` 337 / 333 = 1 餘 4, remainder = 4 40 / 333 = 0 餘 4, remainder = (4 * 10) % 333 400 / 333 = 1 餘 67, remainder = (40 * 10) % 333 = 67 670 / 333 = 2 餘 4, remainder = (67 * 10) % 333 = 4 在 hashmap 中發現相同的 remainder 數值 --> 利用 pos 尋找循環數的起點,插入 ( 與 )並輸出結果 輸出 1.(012) ``` 因此可以取 PPP 為 ```pos--```解答。 <!-- > 參考自 [laneser quzi2](https://hackmd.io/@laneser/linux2022-quiz2#%E6%B8%AC%E9%A9%97-5)筆記 --> 在可能實作的程式碼中,使用 hashmap 的方式儲存 remainder,共有 1333 個 index,並使用 Chaining 的方式解決 Collision。可以得出其 hash function 為 $k \space (mod \space 1333)$ 因此可以推導出 EEE 為 ```&(heads + (remainder % size ))``` ### 測驗 ```6``` ```c= /* * ALIGNOF - get the alignment of a type * @t: the type to test * * This returns a safe alignment for the given type. */ #define ALIGNOF(t) \ ((char *)(&((struct { char c; t _h; } *)0)->M) - (char *)X) ``` 取 M 為 ```_h``` 取 X 為 ```0``` > https://hackmd.io/@sysprog/c-pointer#Null-pointer :::spoiler 神奇的問題 My Linux System got broken when I test with the following code, using ```gcc main.c; ./a.out``` command. It is kinda fun. ```c #include <stdio.h> #include <stdalign.h> int main(){ size_t a = alignof(int); printf("%ld\n", a); struct foo{ char c; int _h; }; // char b = (char *)( &(((struct foo *)0)->c) - ((char *) )); char b = (char *) ( &(((struct foo *)0)->c) ); printf("%d\n", b); return 0; } ``` ``` ➜ ~ gcc --version gcc (Ubuntu 9.4.0-1ubuntu1~20.04) 9.4.0 Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ``` ::: ### 測驗 ```7``` ```c= static inline bool is_divisible(uint32_t n, uint64_t M) { return n * M <= M - 1; } static uint64_t M3 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 3 + 1; static uint64_t M5 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 5 + 1; int main(int argc, char **argv) { for (size_t i = 1; i <= 100; i++) { uint8_t div3 = is_divisible(i, M3); uint8_t div5 = is_divisible(i, M5); unsigned int length = (2 << KK1) << KK2; char fmt[9]; /* 原題目為 * strncpy(fmt, &"FizzBuzz%u"[(9 >> div5) >> (KK3)], length); * 但是會令答案無解,但若是改成下列程式碼,則能正確輸出。 */ strncpy(fmt, &"FizzBuzz%u"[(8 >> div5) >> (KK3)], length); fmt[length] = '\0'; printf(fmt, i); printf("\n"); } return 0; } ``` 取 KK1 為 ```div3``` 取 KK2 為 ```div5``` 取 KK3 為 ```div3 << 2``` 列出其增值表 | div3 | div5 | (2 << KK1) << KK2 | KK1 | KK2 | |:----:|:----:|:-----------------:|:---:|:---:| | 0 | 0 | 2 | 0 | 0 | | 0 | 1 | 4 | 0 | 1 | | 1 | 0 | 4 | 1 | 0 | | 1 | 1 | 8 | 1 | 1 | 因此取 KK1 為 ```div3```, KK5 取 ```div5```。兩者答案可以對調。 原題目 ```strncpy(fmt, &"FizzBuzz%u"[(9 >> div5) >> (KK3)], length);``` 會令輸出產生 ```u``` 而非 ```%u```,因此改為 ```strncpy(fmt, &"FizzBuzz%u"[(8 >> div5) >> (KK3)], length);``` 考慮 strncpy 複製的範圍,列出其增值表 | div3 | div5 | 8 >> div5 >> (KK3) | 預期的 KK3 | |:----:|:----:|:------------------:|:---:| | 0 | 0 | 8 | 0 | | 0 | 1 | 4 | 0 | | 1 | 0 | 0 | 4 | | 1 | 1 | 0 | 4 | 可以發現 KK3 數值一直為 div3 的四倍,因此,取 ```div3 << 2```