--- tags: linux2023-homework2 --- > [Linux 核心設計/實作 (Spring 2023)](http://wiki.csie.ncku.edu.tw/linux/schedule) ## 作業要求 > [2023q1 Homework2 (作業區)](https://hackmd.io/@sysprog/linux2023-homework2) > [L03: quiz2](https://hackmd.io/@sysprog/H143OpNCo) - [ ] 回答第 2 周測驗題從測驗一到測驗四 (採用 2 月 23 日晚間定案的題目),附帶的「延伸問題」也需要完成 - [ ] 解釋程式運作原理時,應提供對應的 [Graphviz](https://graphviz.org/) 圖例,可參照 [Linked List 題目 1 + 分析](https://hackmd.io/@sysprog/linked-list-quiz) - [ ] 比照 [課前測驗參考解答: Q1](https://hackmd.io/@sysprog/bitwise-reverse?type=view), [Linked list 題目分析](https://hackmd.io/@LinYunWen/HyELy5bTz?type=view) 和 [參考題解](https://hackmd.io/@RinHizakura/BysgszHNw) 的模式來撰寫共筆,需要詳細分析自己的思路、參閱的材料 (以第一手材料為主,包含 C 語言規格書的章節),以及進行相關實驗。 ## 測驗 `1` 參照 [你所不知道的 C 語言: bitwise 操作](https://hackmd.io/@sysprog/c-bitwise) ,考慮 `next_pow2` 可針對給定無號 $64$ 位元數值 $x$ ,找出最接近且大於等於 $2$ 的冪的值,例如: * `next_pow2(7)` = 8 * `next_pow2(13)` = 16 * `next_pow2(42)` = 64 ### 運作原理 * 在二進位中,每個位元皆可各自表示為 $2$ 的冪之值 (e.g. `0001` = $2^{0}$, `0010` = $2^{1}$, `0100` = $2^{2}$, `1000` = $2^{3}$) 。 * 利用 bitwise 操作,將原本數值 `x` 中,從最高位元的 $1$ 至最低位元中的每個位元皆設定為 $1$ (e.g. `00001101` -> `00001111` ) 。 * 設定最高位元至最低位元為 $1$ 後再加 $1$ ,即可進位為 $2$ 的冪的值 (e.g. `00001111 + 1` = `00010000` = $2^{4}$) 。 ```c= uint64_t next_pow2(uint64_t x) { x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 8; // AAAA = 8 x |= x >> 16; x |= x >> 32; // BBBB = 32 return x + 1; // CCCC = x + 1 } ``` ### 延伸問題 ## 測驗 `2` LeetCode [1680. Concatenation of Consecutive Binary Numbers](https://leetcode.com/problems/concatenation-of-consecutive-binary-numbers/) 給定一整數 $n$ ,回傳將 $1$ 到 $n$ 的二進位表示法依序串接在一起所得到的二進位字串,其所代表的十進位數字 $mod$ $10^{9}$ + $7$ 之值。 * Example $2$ : ```typescript Input: n = 3 Output: 27 Explanation of solution: i !(i & (i - 1)) len ans 1 -> 1 1 1 1 2 -> 10 1 2 1 10 3 -> 11 0 2 1 10 11 = 27(dec) ``` ### 運作原理 * 每一個 $2$ 的冪之值,各自可作為二進位表示法中的最小 bit length 之值 (e.g. `100` 為 $len$ = $3$ 最小值, `111` 為 $len$ = $3$ 最大值) 。 * 因為要與上一個連續 Binary Number 串接前,需考慮到目前 Binary Number 的預留位元長度。所以判斷要串接的 Binary Number 之位元長度時,只需考慮到最小 bit length 之值,以此來決定位元長度 `len` 之值。 * 對照上述 Example $2$ , `if(!(DDDD))` 為判斷是否為 $2$ 的冪之值 (位元長度 = `i` 的最小值) 。 * `(i | (EEEE))` 則為左移預留長度,並與上一個連續 Binary Number 進行 bitwise 串接。 ```c= int concatenatedBinary(int n) { const int M = 1e9 + 7; int len = 0; long ans = 0; for (int i = 1; i <= n; i++) { /* removing the rightmost set bit * e.g. 100100 -> 100000 * 000001 -> 000000 * 000000 -> 000000 * after removal, if it is 0, then it means it is power of 2 * as all power of 2 only contains 1 set bit * if it is power of 2, we increase the bit length */ if (!(i & (i - 1))) // DDDD = i & (i - 1) len++; ans = (i | (ans << len)) % M; // EEEE = ans << len } return ans; } ``` ### 延伸問題 ## 測驗 `3` UTF-8 字元可由 $1$, $2$, $3$, $4$ 個位元組構成。其中單一位元組的 UTF-8 由 ASCII 字元構成,其 MSB 必為 `0`。 UTF-8 的多位元組字元是由一個首位元組和 $1$, $2$ 或 $3$ 個後續位元組 (continuation byte(s)) 所構成。後續位元組的最高 $2$ 個位元會設定為 `10` 。 | ASCII | 0xxx.xxxx | | -------- | -------- | | 2 bytes | 110x.xxxx 10xx.xxxx | | 3 bytes | 1110.xxxx 10xx.xxxx 10xx.xxxx | | 4 bytes | 1111.0xxx 10xx.xxxx 10xx.xxxx 10xx.xxxx | 若輸入的字串是一個有效的 UTF-8 序列,則計算其中的後續位元組數量,並將此數字從總字串長度中減去,即可確定字元數量。 ### 運作原理 * 逐行拆解程式碼,將原本傳入並佔用一個 byte 的字串 `buf` ,轉換其型態成 unsigned integer type (64-bit),並且佔用 $8$ 個 byte 。 * 從原本 `buf[0], buf[1], .. , buf[7]`的 $8$ 次處理,調整為 `qword[0]` 的 $1$ 次處理。意即讀取 `qword[0]` 同為一次讀取 `buf` 中 index 0~7 的陣列元素。並且將 `end` 指向 `qword[len >> 3]` 的位址。 * `for` 迴圈中輸入的 `t0` 一共包含 $8$ 個位元組,進行 `not bit6 and bit7` 操做,以 population count 指令來計算其中的後續位元組數量 **(不是 UTF-8 字元數量)**。 * `(1 << 3) * (len / 8)` 可得到 $8$ 的倍數,再減去 `count` 來得到 UTF-8 字元數量。 * 最後,若 `len` 不為 $8$ 的倍數,則以 `count_utf8` 來計算 `buf` 中的 UTF-8 字元個數。 ```c= size_t swar_count_utf8(const char *buf, size_t len) { const uint64_t *qword = (const uint64_t *) buf; const uint64_t *end = qword + (len >> 3); size_t count = 0; for (; qword != end; qword++) { const uint64_t t0 = *qword; const uint64_t t1 = ~t0; const uint64_t t2 = t1 & 0x04040404040404040llu; const uint64_t t3 = t2 + t2; const uint64_t t4 = t0 & t3; count += __builtin_popcountll(t4); } count = (1 << 3) * (len / 8) - count; // AAAA = 3 count += (len & 7) ? count_utf8((const char *) end, len & 7) : 0; // BBBB = 7, CCCC = 7, DDDD = 0 return count; } ``` ### 延伸問題 ## 測驗 `4` 判定 $16$ 位元無號整數是否符合特定樣式 (pattern) ```typescript= Valid pattern: pattern: 8000 (32768) -> 1000 0000 0000 0000 pattern: c000 (49152) -> 1100 0000 0000 0000 pattern: e000 (57344) -> 1110 0000 0000 0000 pattern: f000 (61440) -> 1111 0000 0000 0000 pattern: f800 (63488) pattern: fc00 (64512) pattern: fe00 (65024) pattern: ff00 (65280) . pattern: ff80 (65408) . pattern: ffc0 (65472) . pattern: ffe0 (65504) pattern: fff0 (65520) pattern: fff8 (65528) pattern: fffc (65532) pattern: fffe (65534) -> 1111 1111 1111 1110 pattern: ffff (65535) -> 1111 1111 1111 1111 ``` ### 運作原理 * 直觀解法可參照測驗說明的程式碼,從最高位元往低位元逐一檢查所有的 `1` 是否皆連續相鄰。 * 改寫測驗說明中的程式碼,以避免使用到迴圈。首先觀察 `(n ^ x) < FFFF` 的特性,exclusive or `^` 的邏輯運算,可透過 `(x ^ ~x)` 將二進位數字的所有位元設定為 $1$ 。 * 若把 `x` 作 $2$'s complement `(~x + 1)` ,之後再進行 exclusive or `^` 運算,得到的結果皆比原數少一個最低位的 $1$ (e.g. `11110000` vs `11100000`) ,並且皆小於原數。 * 反之,若不符合測驗中的特定樣式,則 `x` 作 $2$'s complement 之後,無法將最低位 $1$ 後的位元皆清零 (e.g. `~(11110100) + 1 = 00001100`) 。進行 exclusive or `^` 運算後,反而會大於原數 (連續相鄰的 `1` 皆被保留) 。 ```c= bool is_pattern(uint16_t x) { const uint16_t n = ~x + 1; // EEEE = ~x + 1 return (n ^ x) < x; // FFFF = x } ``` ### 延伸問題