# 2020q3 Homework3 (quiz3) contributed by < `chi-ming5566` > > [測驗題](https://hackmd.io/@sysprog/2020-quiz3) ## 作業要求 * 重新回答[第 3 周測驗題](https://hackmd.io/@sysprog/2020-quiz3),連同附帶的「延伸問題」。 * 比照 [課前測驗參考解答: Q1](https://hackmd.io/s/ByzoiggIb)的模式來撰寫共筆,需要詳細分析自己的思路、參閱的材料 (以第一手材料為主,包含 C 語言規格書的章節),以及==進行相關實驗==。 --- ### `測驗一` ```cpp= int asr_i(signed int m, unsigned int n) { const int logical = (((int) -1) OP1) > 0; unsigned int fixu = -(logical & (OP2)); int fix = *(int *) &fixu; return (m >> n) | (fix ^ (fix >> n)); } ``` ars 在右移後,會依照原本首位的 bit 補回值,也就是正數補 0,負數補 1。 一開始的 `logical` 判斷環境是不是 logical right shift (一律補 0),所以對 -1 右移 1(`OP1 = >> 1`),如果是 logical right shift,會變成 0b011...111 > 0 = true = 1,反之則是 0b111...111 < 0 = false = 0。 而fixu 用來判斷被除數 m 是不是負數(`OP2 = m < 0`),當 `logical` = 1 與 m < 0 時,`fixu` = -(1 & 1) = 0b111...111,否則 `fixu` = 0b000...000。 return 的結果,如果在 `logical` = 1 以及 m < 0 都成立時,`fix ^ (fix >> n)` 會是前 n 個 bits 為 1,其餘為 0。所以和 `(fix ^ (fix >> n))` 做 OR 運算就可以把 logical right shift 吃掉的 1 補回來。 * Example : m = 0b11100110, n = 4, system = logical ```graphviz digraph { m [shape = plaintext] intm [label = "1|1|1|0|0|1|1|0" shape = record] } ``` ```graphviz digraph { m_shift [label = "m >> n" shape = plaintext] intm_shift [label = "0|0|0|0|1|1|1|0" shape = record] } ``` ```graphviz digraph { fix [shape = plaintext] intfix [label = "1|1|1|1|1|1|1|1" shape = record] } ``` ```graphviz digraph { fix_shift [label = "fix ^ (fix >> n)" shape = plaintext] intfix_shift [label = "1|1|1|1|0|0|0|0" shape = record] } ``` ```graphviz digraph { return [shape = plaintext] intreturn [label = "1|1|1|1|1|1|1|0" shape = record] } ``` --- ### `測驗二` 我們可用 __builtin_ctz 來實作 LeetCode 342. Power of Four,假設 int 為 32 位元寬度。實作程式碼如下: ```cpp= bool isPowerOfFour(int num) { return num > 0 && (num & (num - 1))==0 && !(__builtin_ctz(num) OPQ); } ``` 要判斷輸入的數字是否為 4 的次方,4 的二進位表示是 0100,也就是說 4 的次方是 1 後面接著偶數個 0 (每次乘以 4, 1 就會向左位移 2 位元)。 一開始 `num > 0` 判斷是否為正整數,`(num & (num - 1)) == 0` 判斷是否為 2 的次方 ($1{\overbrace{000...000}^{n個}}$ & $0{\overbrace{111...111}^{n個}} = 0$) ,所以可以知道 `!(__builtin_ctz(num) OPQ` 是在判斷後面的 0 的個數是否為偶數。而任一偶數 & 1 = 0 (偶數最後一項 bit 為 0),所以 `OPQ = & 0x1`。 --- ### `測驗三` popcount 或叫 sideways sum,是計算數值的二進位表示中,有多少位元是 1,在一些場合下很有用,例如計算 0-1 稀疏矩陣 (sparse matrix)或 bit array 中非 0 元素個數、計算兩個字串的 Hamming distance。Intel 在 2008 年 11 月 Nehalem 架構的處理器 Core i7 引入 SSE4.2 指令集,其中就有 CRC32 和 POPCNT 指令,POPCNT 可處理 16-bit, 32-bit, 64-bit 整數。 ```cpp= int numberOfSteps (int num) { return num ? AAA + 31 - BBB : 0; } ``` 這題計算一個數經過多少次計算可以變為零,而計算有分兩種 : >> 1 (當前是偶數)以及 - 1 (當前是奇數)。 int 寬度為 32 bits,因此將最左邊的 bit 移到最右邊要做 31 次 >> 1,有一個 1 就需要做一次 - 1,而起始 1 的位置每往右一個位元就能少做一次 >> 1。 所以 `AAA = __builtin_popcount(num)`, `BBB = __builtin_clz(num)` 。 --- ### `測驗四` 考慮以下 64-bit GCD (greatest common divisor, 最大公因數) 求值函式: ```cpp= #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; } ``` 改寫為以下等價實作: ```cpp= #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 (XXX); return YYY; } ``` --- ### `測驗五` 在影像處理中,bit array (也稱 bitset) 廣泛使用,考慮以下程式碼: ```cpp= #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; } ``` 可用 clz 改寫為效率更高且等價的程式碼: ```cpp= #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 = KKK; int r = __builtin_ctzll(bitset); out[pos++] = k * 64 + r; bitset ^= t; } } return pos; } ``` 兩段程式碼互相比對後,用 `int r = __builtin_ctzll` 取代 `if ((bitset >> i) & 0x1)` 的功能,但改寫後的程式碼對 `out[pos++]` 賦值後還必須 clear bitset 的 LSB 以確保下個 Loop 的正確運行。 > 根據題三提到 $-n=∼n+1$ 利用 `bitset & -bitset` 留下 LSB 再於 `bitset ^= t` 清除 LSB 因此 KKK = `(e) bitset & -bitset`。