--- tags: linux2022 --- # 2022q1 Homework2 (quiz2) contributed by < [Duodenum](https://github.com/Duodenum87) > > [作業要求](https://hackmd.io/@sysprog/BybKYf5xc) ## 2022q1 第 2 週測驗題 ### 測驗 `1` 將前者程式碼改成後者 ```c #include <stdint.h> uint32_t average(uint32_t a, uint32_t b) { return (a + b) / 2; } ``` ```c #include <stdint.h> uint32_t average(uint32_t a, uint32_t b) { return (a >> 1) + (b >> 1) + (EXP1); } ``` 其中 `(a >> 1) + (b >> 1)` 相當直覺為兩者除以二後相加,但各自的最後一個 bit 將會流失,故 `EXP1` 即須將此資訊補回。 而只有當 a 即 b 的最後一個 bit 皆 set 時,相加才會需要進位。也就是說 `EXP1` == `(a & 1) & (b & 1)`。 繳出後才發現需要化到最簡,即 `EXP1` == `a & b & 1` 。 再次改寫成以下 ```c uint32_t average(uint32_t a, uint32_t b) { return (EXP2) + ((EXP3) >> 1); } ``` 看到題目第一直覺要填 `(a + b) >> 1` ,但與題目要求不符。直接餵狗 [average bitwise operation](https://stackoverflow.com/questions/1533131/what-useful-bitwise-operator-code-tricks-should-a-developer-know-about) 其中五樓回復到答案 `(a & b) + ((a ^ b) >> 1)` ```c static inline ulong average(ulong x, ulong y) // Return floor( (x+y)/2 ) // Use: x+y == ((x&y)<<1) + (x^y) // that is: sum == carries + sum_without_carries { return (x & y) + ((x ^ y) >> 1); } ``` 其中 `x + y == ((x & y) << 1) + (x ^ y)` 即為加法器實作。 ### 測驗 `2` 參考 [解讀計算機編碼](https://) 一文中的 branchless `min` 如下: ```c // find the smaller one of two integer int32_t min(int32_t a, int32_t b) { int32_t diff = (a - b); return b + (diff & (diff >> 31)); } ``` 即透過 `(a - b)` 為正或負來使得 `b + (a - b) = b` 或 `b + (-(a - b)) = a` 。 接著實作 branchless `max`: ```c #include <stdint.h> uint32_t max(uint32_t a, uint32_t b) { return a ^ ((EXP4) & -(EXP5)); } ``` 推理過程如下 | step 1 | a > b | a < b | | -------- | -------- | -------- | | OUTPUT |a ^ () = a | a ^ () = b| |() = | 0 | a ^ b| 為了使得 `((EXP4) & -(EXP5))` 達成 `0` 或 `a ^ b` | step 2 | a > b | a < b| | -------- | -------- | -------- | | EXP4 | a ^ b | a ^ b | | -(EXP5) | 0 | -1 | | EXP5 | 0 | 1 | 即可得出 `EXP5` == `a < b` ### 測驗 `3` 考慮以下 64 位元 GCD (greatest common divisor, 最大公因數) 求值函式: ```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) { // 1. and 2. if (!u || !v) return u | v; // 3. int shift; for (shift = 0; !((u | v) & 1); shift++) { u /= 2, v /= 2; } // 4. while (!(u & 1)) u /= 2; do { // 5. while (!(v & 1)) v /= 2; // 6. if (u < v) { v -= u; } else { uint64_t t = u - v; u = v; v = t; } } while (COND); return RET; } ``` 分別對應了以下 GCD 演算法 1. if both x, y are 0, $gcd(0,0) = 0$ 2. $gcd(x,0)=x$ and $gcd(0,y)=y$ 3. if x, y are both even, $gcd(x,y)=2*gcd(\frac{x}{2},\frac{y}{2})$ 4. If x is even and y is odd, then $gcd(x,y)=gcd(\frac{x}{2},y)$ 5. If x is odd and y is even, then $gcd(x,y)=gcd(x,\frac{y}{2})$ 6. [輾轉相除法](https://en.wikipedia.org/wiki/Euclidean_algorithm) ,透過不斷相減求出相除會有的餘數結果。其中 while 迴圈的判斷條件,因為以 `v` 作為餘數, `COND` == `v` 7. `RET` 的值即為最大公因數,但因為先前的操作中已經先將所有 2 因數取出並存在 `shift` 中。需要重新乘回,也就是 `u * s ^ shift` = `u << shift` ### 測驗 `4` 在影像處理中,bit array (也稱 bitset) 廣泛使用,考慮以下程式碼: ```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; } ``` 以 `__builtin_ctzll` 改寫成以下 ```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; } ``` `TODO` ### 測驗 `5` 以下是 LeetCode [166. Fraction to Recurring Decimal](https://leetcode.com/problems/fraction-to-recurring-decimal/) 的可能實作: ```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; } ``` `TODO` ### 測驗 `6` `__alignof__`是 GNU extension,以下是其可能的實作方式: ```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) ``` 令一個起始位址為 0 的 struct 指標,且其指向成員 M ,再求出其地址,最後減去 X 因為 struct 中只有一成員, `M` = `_h` 為求型態 t 的 alignment , `X` = `0` ### 測驗 `7` 實做 FizzBuzz: ```c // naive #include <stdio.h> int main() { for (unsigned int i = 1; i < 100; i++) { if (i % 3 == 0) printf("Fizz"); if (i % 5 == 0) printf("Buzz"); if (i % 15 == 0) printf("FizzBuzz"); if ((i % 3) && (i % 5)) printf("%u", i); printf("\n"); } return 0; } ``` ```c // bitwise 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); fmt[length] = '\0'; printf(fmt, i); printf("\n"); } return 0; } ```