蔡忠翰
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 2024 Homework4 (quiz3+4) ## 第三周測驗 [2024q1 第 3 週測驗題](https://hackmd.io/@sysprog/linux2024-quiz3) ### 測驗 `1` : Using bitwise operations to compute square root 先看原程式碼 ```c #include <math.h> int i_sqrt(int N) { int msb = (int) log2(N); int a = 1 << msb; int result = 0; while (a != 0) { if ((result + a) * (result + a) <= N) result += a; a >>= 1; } return result; } ``` 編譯方式 ``` $ gcc -o i_sqrt i_sqrt.c -lm ``` > 因為呼叫 log2,所以需要連結 libm (math library),即 -lm 選項 假設 N = $36_{10}$ =$100100_2$,`msb = 5` => a = $100000_2$ 接著進入迴圈中 | 迴圈次數 | a | result |是否進入迴圈| | -------- | -------- | -------- |- | | 1 | $100000_2$ | 0 |y | | 2 | $10000_2$ | 0 |y | | 3 | $1000_2$ | 0 |y | | 4 | $100_2$ | $100_2$ |y | | 5 | $10_2$ | $110_2$ |y | | 6 | $1_2$ | 0 |n | 在此 `bitwise` 操作中, `(result + a)` 的平方逐一比較 `N` 值,當值較小時就疊加當前的值,即 `result += a` ,直到為 N 的開方根值。 > 此題一開始用十進位去看比較容易明白, `a >>= 1` 看作 `a / 2`。 編譯方式:`$ gcc -o i_sqrt i_sqrt.c -lm` 版本二使用右移操作直到 n 值為 0 ,藉此計算 `msb` ```clike int msb = 0; int n = N; while (n > 1) { n >>= 1; msb++; } ``` 版本三使用 [Digit-by-digit calculation](https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Digit-by-digit_calculation) 方法進行開方根。將平方數拆成 2 的冪相加 令 $x=N^2=(a_n+a_{n-1}+...+a_0)^2$ ,其中 N 即為所求的開方根 $P_m=a_n+a_{n-1}+...+a_m$ $P_0=a_n+a_{n-1}+...+a_0=N$ 即為所求 推論出: $P_m=P_{m+1}+a_m$ 後續將求出所有 $a_m=2^m$ or $0$,從 $m = n$ 一直到 $m = 0$,而求得 $a_m$ 只須比較$P_m^2 \le N^2$,有兩種情況: \begin{cases} P_m=P_{m+1}+2^m \ \ ;\ P_m^2 \le N^2\\ P_m=P_{m+1}\ \ \ \ \ \ \ \ \ \ \ \ ;\ otherwise \end{cases} 由於每輪逐一比較的運算成本過高,提出了另個想法 將$N^2-P_m^2$ 的值表示位$X_m$ \begin{cases} X_m=N^2-P_m^2--(1) \\ X_{m+1}=N^2-P_{m+1}^2--(2) \end{cases} 將(1)式減(2)式可得 $X_m-X_{m+1}=-P_m^2+P_{m+1}^2--(3)$ 其中 $P_m$ 平方為 $P_m^2=P_{m+1}^2+2a_mP_{m+1}+a_m^2=P_{m+1}^2+(2P_{m+1}+a_m)a_m$ 令 $Y_m= P_m^2-P_{m+1}^2=(2P_{m+1}+a_m)a_m$ 最後將 $Y_m$ 代回到(3)式可得: $X_m=X_{m+1}-Y_m$ 將 $Y_m$ 拆解 $c_m,d_m$ $c_m=P_{m+1}2^{m+1}$ $d_m=(2^m)^2$ 得: \begin{cases} Y_m=c_m+d_m\ ;if\ a_m=2^m\\ Y_m=0\ \ \ \ \ \ \ \ \ \ \ \ \ \ ;if\ a_m=0 \end{cases} 藉由位元運算從$c_m,d_m$推出下一輪 $c_{m-1}=P_m2^m=(P_{m+1}+a_m)2^m=P_{m+1}2^m+a_m2^m$ \begin{cases} c_m/2+d_m\ \ if\ a_m=2^m\\ c_m/2\ \ \ \ \ \ \ \ \ \ \ \ if\ a_m=0\\ \end{cases} $d_{m-1}=d_m/4$ **Linux 核心原始程式碼** [linux/block/blk-wbt.c](https://github.com/torvalds/linux/blob/39cd87c4eb2b893354f3b850f916353f2658ae6f/block/blk-wbt.c#L9) ```c static void rwb_arm_timer(struct rq_wb *rwb) { struct rq_depth *rqd = &rwb->rq_depth; if (rqd->scale_step > 0) { /* * We should speed this up, using some variant of a fast * integer inverse square root calculation. Since we only do * this for every window expiration, it's not a huge deal, * though. */ rwb->cur_win_nsec = div_u64(rwb->win_nsec << 4, int_sqrt((rqd->scale_step + 1) << 8)); } else { /* * For step < 0, we don't want to increase/decrease the * window size. */ rwb->cur_win_nsec = rwb->win_nsec; } blk_stat_activate_nsecs(rwb->cb, rwb->cur_win_nsec); } ``` 尚未完成 ### 測驗 `2` : Using bitwise operations to perform mod 10 and div 10 參考《[Hacker's Delight](https://web.archive.org/web/20180517023231/http://www.hackersdelight.org/divcMore.pdf)》,採用 bitwise operation 來實作除法 $n\ \dfrac{a}{2^N}$ 此題選用 a=13 N=7 => $n\ \dfrac{13}{2^7}$ ,其中 $\dfrac{13}{2^7}$ 為 $0.10156$ 。 完整程式碼 ```c unsigned d2, d1, d0, q, r; d0 = q & 0b1; d1 = q & 0b11; d2 = q & 0b111; q = ((((tmp >> 3) + (tmp >> 1) + tmp) << 3) + d0 + d1 + d2) >> 7; r = tmp - (((q << 2) + q) << 1); ``` 將 $tmp\times \dfrac{13}{2^7}$ 改寫成 $tmp\times 13 \div 16 \div 8$,其中 $tmp\times \dfrac{13}{8}\times 8=13tmp$ 可從下方程式碼得到 ```c (((tmp >> 3) + (tmp >> 1) + tmp) << 3) ``` 由於 `tmp` 右移三位會遺失原本最低三位的 bit ,因此將分別用 `d0` 、 `d1` 、 `d2` 來儲存原先的 bit ,再將遺失的 bit 補回來,最後在除以128,即為 $\dfrac{13}{128} tmp$,得到一個近似 $tmp\div 10$ 的結果 ( `q` )。 ```c q = ((((tmp >> 3) + (tmp >> 1) + tmp) << 3) + d0 + d1 + d2) >> 7; ``` :::info 上述程式碼中不太理解為何補足遺失的最低三位 bit 是加上 `+ d0 + d1 + d2` ,在我實作中發現就算缺少這段程式碼也不會影響其結果,其大致的觀念我能理解是為了增加其精度,但又為何不直接加上 `+ d2` 就好。 ::: 下方程式碼根據餘數定理:$f-g\times Q=r$,可以得到 $r$ (餘數) - $f$ : 被除式 - $g$ : 除式 - $Q$ : 商 - $r$ : 餘數 ```c r = tmp - (((q << 2) + q) << 1); ``` 其中`(((q << 2) + q) << 1)` 等於 $(q\times 4+q)\times2=q\times 10$ 將程式碼包裝成 ```c #include <stdint.h> void divmod_10(uint32_t in, uint32_t *div, uint32_t *mod) { uint32_t x = (in | 1) - (in >> 2); /* div = in/10 ==> div = 0.75*in/8 */ uint32_t q = (x >> 4) + x; x = q; q = (q >> 8) + x; q = (q >> 8) + x; q = (q >> 8) + x; q = (q >> 8) + x; *div = (q >> 3); *mod = in - ((q & ~0x7) + (*div << 1)); } ``` `uint32_t x = (in | 1) - (in >> 2)` 得到 $\dfrac{3}{4}in$ `uint32_t q = (x >> 4) + x;` 得到 $q=\dfrac{3}{4}in\div 16+\dfrac{3}{4}in=\dfrac{51}{64}in$ ,其中 $\dfrac{51}{64}in$約為 $0.796875in$ 近似 $\dfrac{8}{10}$ 再經由 bitwise 操作使 0.796875 更接近 0.8 ```c q = (q >> 8) + x; // q = 0.799987793 q = (q >> 8) + x; // q = 0.799999952 q = (q >> 8) + x; // q = 0.799999998 q = (q >> 8) + x; // q = 0.8 ``` 最後 `*div = (q >> 3);` 即為 $*div=\dfrac{8}{10}in\div 8=\dfrac{1}{10}in$ `((q & ~0x7) + (*div << 1));` 為 $\dfrac{8}{10}in+\dfrac{1}{10}in\times 2=\dfrac{in}{10}\times10$,其中 $\dfrac{in}{10}$ 為商。 在由餘式定理:$f-g\times Q=r$ 求得 `*mod` 值為何 **練習撰寫不依賴任何除法指令的 % 9 (modulo 9) 和 % 5 (modulo 5) 程式碼** 尚未進行 ### 測驗 `3` : Using bitwise operations to calculate the logarithm base 2 用 `ilog2` 計算以 2 為底的對數,且其輸入和輸出皆為整數。 稍微改寫一下原程式碼方便識讀,舉例 $i = 4_{10} = 100_2$ | while ( i >= 2 ) | 原 i | 後 i | log | | -------- | -------- | -------- | - | | 第 1 次迴圈 | 100 | 10 | 1 | | 第 2 次迴圈 | 10 | 1 | 2 | 輸出 `log = 2` ```diff int ilog2(int i) { - int log = -1; + int log = 0; - while (i){ + while (i >= 2) { i >>= 1; log++; } return log; } ``` 改寫後的程式碼,將 `i` 用一定數量的位元去進行判斷,有效提升數字較大時的計算速度,其原因為原程式碼每個位元逐一去判斷,反覆進行多次的迴圈,改後後判斷分成 4 個階段,若 `i` 大於 $2^{16}、2^8、2^4、2^1$則直接進行位元操作。 ```c static size_t ilog2(size_t i) { size_t result = 0; while (i >= 65536) { result += 16; i >>= 16; } while (i >= 256) { result += 8; i >>= 8; } while (i >= 16) { result += 4; i >>= 4; } while (i >= 2) { result += 1; i >>= 1; } return result; } ``` **在 Linux 核心原始程式碼找出 log2 的相關程式碼 (或類似的形式),並解說應用案例** 看到 log2 為底的形式就想到 `clz` 、 `ffs` 、 `fls` 等相關的操作都有類似的手法。 在 [linux/arch/arc/include/asm/bitops.h](https://github.com/torvalds/linux/blob/026e680b0a08a62b1d948e5a8ca78700bfac0e6e/arch/arc/include/asm/bitops.h#L28) 中找到 fls 的實作,其作用為返回最高有效 bit 位(由最低位往左數),當判斷式為 0 進行位移操作,其手法相似 log2 ,不同於位元操作向左進行。 該順序不同於 `log2` , `constant_fls` 從 1 起始,當沒有有效位返回 0 。 ```c static inline int constant_fls(unsigned int x) { int r = 32; if (!x) return 0; if (!(x & 0xffff0000u)) { x <<= 16; r -= 16; } if (!(x & 0xff000000u)) { x <<= 8; r -= 8; } if (!(x & 0xf0000000u)) { x <<= 4; r -= 4; } if (!(x & 0xc0000000u)) { x <<= 2; r -= 2; } if (!(x & 0x80000000u)) r -= 1; return r; } ``` ### 測驗 `4` : Using bitwise to calculate EWMA > 參考 [stevendd543](https://hackmd.io/@stevennnnnn/linux2024-homework4#%E7%AC%AC%E5%9B%9B%E9%A1%8C)、[2024q1 第 3 週測驗題](https://hackmd.io/@sysprog/linux2024-quiz3#%E6%B8%AC%E9%A9%97-4) **Exponentially Weighted Moving Average** 賦予資料指數衰減的權重,主要目的使越舊的資料權重越低,反之越新的資料對整體平均值的影響越大,以下為此統計手法的公式: $$ S_t = \begin{cases} Y_0 \quad\quad &t=0 \\ \alpha Y_t + (1-\alpha) \cdot S_{t-1} \quad\quad &t>0 \end{cases} $$ - $\alpha$ 表示歷史資料加權降低的程度,介在 0 ~ 1 之間,越高的 $\alpha$ 會使歷史資料減少的越快 - $Y_t$表示時間 t 時的資料點 - $S_{t}$ 表示時間 t 時的 EWMA ```c void ewma_init(struct ewma *avg, unsigned long factor, unsigned long weight) { if (!is_power_of_2(weight) || !is_power_of_2(factor)) assert(0 && "weight and factor have to be a power of two!"); avg->weight = ilog2(weight); avg->factor = ilog2(factor); avg->internal = 0; } ``` `@internal` : 看作 $t$ 當前的位置 `@factor` : 縮放因子,必須是 2 的冪次 `@weight` : 資料的權重,在這 $\alpha=2^{avg->weight}$ 透過右移來達到$\alpha=2^{-(avg->weight)}=\dfrac{1}{2^{avg->weight}}$,必須是 2 的冪次 ```c struct ewma *ewma_add(struct ewma *avg, unsigned long val) { avg->internal = avg->internal ? (((avg->internal << weight) - avg->internal) + (val << avg->factor)) >> avg->weight : (val << avg->factor); return avg; } ``` 一開始 $t=0$ ,因此 $S_t=Y_0$ 對應到 `avg->internal = (val << avg->factor);` ,接著 `avg->internal = (((avg->internal << avg->weight) - avg->internal) + (val << avg->factor)) >> avg->weight` 的理解較為困難,看起來與題意的公式不同,在 [stevendd543](https://hackmd.io/@stevennnnnn/linux2024-homework4#%E7%AC%AC%E5%9B%9B%E9%A1%8C) 筆記中的舉例有非常清楚解釋: 令 $\alpha\alpha^{-1}=1$,將公式改寫成 $\alpha^{-1}S_t=\alpha^{-1}(\alpha Y_t+(1-\alpha)S_{t-1})$ $\ \ \ \ \ \ \ \ \ \ =Y_t+(\alpha^{-1}-1)S_{t-1}$ 其中 $(\alpha^{-1}-1)S_{t-1}$ 等效於 `((avg->internal << avg->weight) - avg->internal)`,$Y_t$ 等效於 `(val << avg->factor)` ,最後將整個公式 `>> avg->weight` 即為 $\alpha^{-1}S_t\times \alpha=S_t$ (當時 $t$ 的EWMA)。 ```c unsigned long ewma_read(const struct ewma *avg) { return avg->internal >> avg->factor; } ``` 將計算的平均值除以縮放因子而得到真正的平均值。 **在 Linux 核心原始程式碼找出 EWMA 的相關程式碼 (或類似的形式),至少該涵蓋無線網路裝置驅動程式** ### 測驗 `5` : Test 3 Extension 延伸測驗 3,已知輸入必為大於 0 的數值 (即 x > 0),以下程式碼可計算 [log2] ,也就是 [ceil](https://man7.org/linux/man-pages/man3/ceil.3.html) 和 log2 的組合並轉型為整數。 `x--` 目的為避免 `x` 為 2 的冪,接著使用類似測驗 `3` 的作法,在指定的位元寬度中比較,若條件成立就會進行位元操作,下方程式碼寫的更加複雜因此實際帶入數值去進一步解釋。 ```c int ceil_ilog2(uint32_t x) { uint32_t r, shift; x--; r = (x > 0xFFFF) << 4; x >>= r; shift = (x > 0xFF) << 3; x >>= shift; r |= shift; shift = (x > 0xF) << 2; x >>= shift; r |= shift; shift = (x > 0x3) << 1; x >>= shift; return (r | shift | x > 1) + 1; } ``` case `1` x = 0000 0000 0000 0000 0000 1000 0000 0000 ``` x-- = 0000 0000 0000 0000 0000 0111 1111 1111 r = (x > 0xFFFF) << 4 = 0 << 4 = 0 x = x >> r = 0000 0000 0000 0000 0000 0111 1111 1111 shift = (x > 0xFF) << 3 = 1 << 3 = 8 x >>= shift = 0000 0000 0000 0000 0000 0000 0000 0111 r |= shift = 8 shift = (x > 0xF) << 2 = 0 << 2 = 0 x >>= shift = 0000 0000 0000 0000 0000 0000 0000 0111 r |= shift = 8 shift = (x > 0x3) << 1 = 1 << 1 = 2 x >>= shift = 0000 0000 0000 0000 0000 0000 0000 0001 return (r | shift | x > 1) + 1 = return (11) ``` - 透過 `shift = (x > 0xFF) << 3` 決定 `x` 要右移的大小 - `r |= shift` 等效 `r += shift` ,用來記錄由第 0 bit 往左數到 msb 的有效位 - 最後結果把先前 `x--` 的值加回來 **改進程式碼,使其得以處理 x = 0 的狀況,並仍是 branchless** ```diff int ceil_ilog2(uint32_t x) { uint32_t r, shift; + bool mark = x; + x -= !!x - x--; r = (x > 0xFFFF) << 4; x >>= r; shift = (x > 0xFF) << 3; x >>= shift; r |= shift; shift = (x > 0xF) << 2; x >>= shift; r |= shift; shift = (x > 0x3) << 1; x >>= shift; - return (r | shift | x > 1) + 1; + return (r | shift | x > 1) + mark; } ``` 當 `x = 0` 時 `ceil_ilog2` 應要回傳 0 ,因此設立一個 `mark` 來紀錄最後是否 +1 。 **Linux 核心原始程式碼** 尚未完成 ## 第四周測驗 [2024q1 第 4 週測驗題](https://hackmd.io/@sysprog/linux2024-quiz4#%E6%B8%AC%E9%A9%97-1) ### 測驗 `1` : Total Hamming Distance 閱讀 [population count](https://en.wikichip.org/wiki/population_count#google_vignette) 了解計算二進位表示式中有多少個位元為 `1` 、 了解 [Hamming distance](https://en.wikipedia.org/wiki/Hamming_weight) 所代表的意思。 更進階的操作參照[測驗1](https://hackmd.io/@sysprog/linux2024-quiz4#%E6%B8%AC%E9%A9%97-1),大致內容為每4個位元為一個單位,求出其中的 set bits 個數,32位元中總共有 8 組,將每組算得的 set bits 個數加總在一起,裡面的推導過程需詳讀就能得知最後為何 `v >> 24` 即為 popcount 。 考題針對 [LeetCode 477. Total Hamming Distance](https://leetcode.com/problems/total-hamming-distance/description/) ```c int totalHammingDistance(int* nums, int numsSize) { int total = 0;; for (int i = 0;i < numsSize;i++) for (int j = 0; j < numsSize;j++) total += __builtin_popcount(nums[i] ^ nums[j]); return total >> 1; } ``` Leetcode 範例描述到 ``` Input: nums = [4,14,2] The answer will be: HammingDistance(4, 14) + HammingDistance(4, 2) + HammingDistance(14, 2) = 2 + 2 + 2 = 6. ``` 在考題程式碼中為兩兩成對的去計算 popcount ,因此會出現HammingDistance(4, 14)與HammingDistance(14, 4)這種情況,因此最後回傳要除以 2 (即 `return total >> 1`)。 **不依賴 popcount,嘗試以上述歸納的規則,針對 [LeetCode 477. Total Hamming Distance](https://leetcode.com/problems/total-hamming-distance/description/) 撰寫出更高效的程式碼** 參考 solution 中的解答 ```c int totalHammingDistance(int* nums, int numsSize){ int ans = 0; for (int i = 0; i < 32; ++i) { int c = 0; for (int j = 0; j < numsSize; ++j) { c += (nums[j] >> i) & 1; } ans += c * (numsSize - c); } return ans; } ``` 使用 bitwise 逐一比較二進制中每個位元是否為 1 ,其判斷式 `(nums[j] >> i) & 1` 每當比較完一位元,便計算其 HammingDistance 的值 ( `ans += c * (numsSize - c)` ) | n 'th bit | 3 | 2 | 1 | 0 | | -------- | -------- | -------- | - | - | | input 4 | 0 | 1 | 0 | 0 | | input 14 |1 | 1 | 1 | 0 | | input 2 | 0 | 0 | 1 | 0 | 1. 第 0 個 bit 觀察: 全部都是 0 => Hamming Distance = 0 2. 第 1 個 bit 觀察: 1 有兩個 => Hamming Distance = (2)* (numsSize - 2) 3. 第 3 個 bit 觀察: 1 有一個 => Hamming Distance = (1)* (numsSize - 1) ### 測驗`2` : Remainder by Summing digits 了解[ [ 離散數學 ] 同餘(Mod)](https://ithelp.ithome.com.tw/articles/10205727)的性質及定義 詳讀 [Hacker's Delight](https://web.archive.org/web/20180517023231/http://www.hackersdelight.org/divcMore.pdf) 中提及 Remainder by Summing digits 以除 3 為例, $1\equiv 1\pmod 3$ , $2\equiv -1\pmod 3$ $2^k\equiv$ \begin{cases} 1\equiv 1\pmod 3, & \text{if $k$ is even} \\ 2\equiv -1\pmod 3, & \text{if $k$ is odd} \end{cases} n 以二進制表示成 $n=b_{n-1}b_{n-2}...b_1b_0$ \begin{aligned} n &= {\sum\limits_{i=0}^{n-1}} b_i2^{i} 由以上推論出\ n \equiv {\sum\limits_{i=0}^{n-1}} b_i(-1)^i\pmod 3 \end{aligned} 將上式結合 popcount 寫成程式 ```c n = popcount(n & 0x55555555) - popcount(n & 0xAAAAAAAA) ``` > 0x55 = 0b01010101 > 0xAA = 0b10101010 再由定理 $popcount(x \And \overline{m}) - popcount(x \And m) = popcount(x \bigoplus m) - popcount(m)$ > 定理證明在 [Hacker's Delight](https://web.archive.org/web/20180517023231/http://www.hackersdelight.org/divcMore.pdf) 裡有提到,頁碼:P13 化簡成 ```c n = popcount(n ^ 0xAAAAAAAA) - 16 ``` ```c int mod3(unsigned n) { n = popcount(n ^ 0xAAAAAAAA) + 23; //n = popcount(n ^ 0xAAAAAAAA) - 16 + 39; n = popcount(n ^ 0x2A) - 3; return n + ((n >> 31) & 3); //避免 n 為負數值 } ``` 為了避免 `n` 變成負數,因此加上一個足夠大的 3 的倍數,範例中為加 39 ,在第二輪應用使 `n` 介於 -3~2 之間而非 -3~3 之間。 `return n + ((n >> 31) & 3)`主要用作避免 n 為負數值,若為 n 為正則不受影響。 ``` 舉例 若 n = -1 -1 -> 0b1111 1111 1111 1111 1111 1111 1111 1111 # n 0b11 # (n >> 31) & 3 0b10 # n + ((n >> 31) & 3) ``` lookup table ```c int mod3(unsigned n) { static char table[33] = {2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1 }; n = popcount(n ^ 0xAAAAAAAA); return table[n]; } ``` 使用查表的方式去尋找對應的餘數。 **tictactoe.c** **將上述手法應用於第三次作業中,追求更有效的賽局判定機制。** ### 測驗 `3` : XTree [AVL Tree](https://josephjsf2.github.io/data/structure/and/algorithm/2019/06/22/avl-tree.html) 是一種Binary Search Tree (BST) 實作方法,透過高度計算及 balance factor 判斷是否需要旋轉來平衡。 [red-black tree](https://clu.gitbook.io/data-structure-note/1.4.3-red-black-tree) 遵循以下規則來達成平衡: 1. 節點只有黑或紅 2. 根節點必為黑 3. 若節點是紅色的,則其子節點必為黑色,反之不必為真(黑色節點的下個節點可為黑或紅) 4. 所有從該節點走到其子樹下的末端節點 (leaf) 的上之黑色節點數量必定 此題 XTree 的平衡機制與 AVL tree 相似,利用 hint 來評估是否需要做 rotate,然而 xt_node 的 hint 的計算方式是,其左右節點 hint 最大值 +1 並且只有在被呼叫到 xt_update 時才會被更新。 ```c #define xt_root(r) (r->root) #define xt_left(n) (n->left) #define xt_right(n) (n->right) #define xt_rparent(n) (xt_right(n)->parent) #define xt_lparent(n) (xt_left(n)->parent) #define xt_parent(n) (n->parent) ``` **`xt_create`** : 初始化 `XTree` **`xt_destroy`** 利用遞迴 ```c if (xt_root(tree)) __xt_destroy(tree, xt_root(tree)); ``` 將樹裡的每個節點釋放,在 `__xt_destroy()` 中 ```c if (xt_left(n)) __xt_destroy(tree, xt_left(n)); if (xt_right(n)) __xt_destroy(tree, xt_right(n)); ``` 若 `xt_left` (左子樹)或 `xt_right` (右子樹)存在則繼續往下走直到盡頭 `tree->destroy_node(n)` 來釋放。 **`xt_first`** 用遞迴的方式尋找最左邊的節點 ```c if (!xt_left(n)) return n; return xt_first(xt_left(n)); ``` **`xt_last`** 與 `xt_first` 一樣,但變成尋找最右變的節點 **`xt_rotate_left`** ```c struct xt_node *l = xt_left(n), *p = xt_parent(n); xt_parent(l) = xt_parent(n); xt_left(n) = xt_right(l); xt_parent(n) = l; xt_right(l) = n; ``` 這段程式碼為下圖操作 ```graphviz digraph Tree { graph[ordering=out] p -> n n -> l n -> r l -> A l -> B label=before } ``` ```graphviz digraph Tree { graph[ordering=out] p -> l l -> A l -> n n -> B n -> r label=after } ``` ```c if (p && xt_left(p) == n) xt_left(p) = l; else if (p) xt_right(p) = l; ``` 假設 p 的右子樹不存在 ```graphviz digraph Tree { graph[ordering=out] null [label="" shape=plaintext] null1 [label="" shape=plaintext] p -> l p -> null l -> null1 l -> n label=before } ``` ```graphviz digraph Tree { graph[ordering=out] null [label="" shape=plaintext] null1 [label="" shape=plaintext] p -> null p -> l l -> null1 l -> n label=after } ``` ```c if (xt_left(n)) xt_lparent(n) = n; ``` 重新定義 n 節點的 parent **`xt_rotate_right`** 操作手法與 `xt_rotate_left` 相似,因此僅用圖示 ```graphviz digraph Tree { graph[ordering=out] p -> n n -> l n -> r r -> A r -> B label=before } ``` ```graphviz digraph Tree { graph[ordering=out] p -> r r -> n n -> l n -> A r -> B label=after } ``` **`xt_balance`** 與 AVL Tree 中 Balance Factor of T, BF(T) 的平衡判斷相似,這裡的 hint 為左子樹高 - 右子樹高 **`xt_max_hint`** 選擇左子樹與右子樹的 hint 的最大值 `+1` 即為節點 n 的 hint 值(更新 hint) **`xt_update`** 若樹不平衡 `xt_balance < -1` 進行 `xt_rotate_right` , `xt_balance > 1` 進行 `xt_rotate_left` **`xt_insert`** 插入新節點在樹中 **`xt_remove`** 當移除一節點 `del` 時,將該右子樹的最左來填補,即 `least` 來替換掉原本的 `del` ,並檢查是否平衡 `xt_update`,反之則用左子樹的最右節點來點補,若被刪除的節點為 `leaf` 將不用進行交換。 ```c static void __xt_remove(struct xt_node **root, struct xt_node *del) { if (xt_right(del)) { struct xt_node *least = xt_first(xt_right(del)); if (del == *root) *root = least; xt_replace_right(del, least); xt_update(root, xt_right(least)); return; } if (xt_left(del)) { struct xt_node *most = xt_last(xt_left(del)); if (del == *root) *root = most; xt_replace_left(del, most); xt_update(root, xt_left(most)); return; } if (del == *root) { *root = 0; return; } /* empty node */ struct xt_node *parent = xt_parent(del); if (xt_left(parent) == del) xt_left(parent) = 0; else xt_right(parent) = 0; xt_update(root, parent); } ```

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully