# 2023q1 Homework2 (quiz2) contributed by<charlie0822> ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 36 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 Vendor ID: GenuineIntel Model name: Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz CPU family: 6 Model: 58 Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 Stepping: 9 CPU max MHz: 3900.0000 CPU min MHz: 1600.0000 BogoMIPS: 6784.20 ``` ## 測驗一 ```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 >> AAAA; x |= x >> 16; x |= x >> BBBB; return CCCC; } ``` 請補完程式碼,使其符合預期。 ### 想法與思考 自己先從8位元開始思考,`next_pow2(64)`=128,每向右一次就擴展一個0,擴展6次就會有連續7個1,最後加1進位就可以得到下一個2的冪 ``` 01000000 | v 01111111 (連續向右擴展 2 ) | v 10000000 (加1可以得到下一個 2 的冪) ``` 把邏輯帶入 64 位元的話就是要將 x 最高位元 1 後面位元都 set 成 1 ,最後加 1 進位到下一個 2 的冪返回。 `AAAA=8 BBBB=32 CCCC=x+1` ### 用 __builtin_clzl 改寫 ```c uint64_t next_pow2(uint64_t x) { return 1 << (64 - __builtin_clzl(x)); } ``` ## 測驗二 ```c int concatenatedBinary(int n) { const int M = 1e9 + 7; int len = 0; /* the bit length to be shifted */ /* use long here as it potentially could overflow for int */ 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 (!(DDDD)) len++; ans = (i | (EEEE)) % M; } return ans; } ``` 請補完程式碼,使其符合預期。 ### 想法與思考 if 判斷 i 是否是 2 的冪,因為當 i 是 2 的冪位元需要多一位,所以len需要加 1 ,在判斷中 DDDD 為 `i&(i-1)`,當 i 是 2 的冪時,i&(i-1)會剛好為 0 ,最後將ans向左移 len 將 i 加入到 ans 裡,故 EEEE為 `ans<<len`。 ### 使用 __builtin_{clz,ctz,ffs}改寫 ```c int concatenatedBinary(int n) { const int M = 1e9 + 7; int len = 0; long ans = 0; for (int i = 1;i <= n; i++) { if(__builtin_popcount(i)== 1){ len++; } ans = (i | ans << len) % M; } return ans; } ``` ``` Built-in Function: int __builtin_popcount (unsigned int x) Returns the number of 1-bits in x. ``` 當 i 是2的冪時,代表位元中只會有1個1,需要將 len 需要加1。 ## 測驗三 ```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 << AAAA) * (len / 8) - count; count += (len & BBBB) ? count_utf8((const char *) end, len & CCCC) : DDDD; return count; } ``` ### 想法與思考 一開始將 char *buf 轉換成 uint64,從處理 1 byte 改成可以 1 次處理8bytes, end 計算出總共有幾組的 8 bytes需要處理,進入迴圈開始計算 count 數,將字串長度減去 count 數就可以得到字元數量,由於 buf 不一定是 8 的倍數,最後有可能會有 0 ~ 7 個 bytes 需要處理, len % 8 = len & 7,故 `AAAA=3,BBBB=7,CCCC=7,DDDD=0`。 ## 測驗四 ```c #include <stdint.h> #include <stdbool.h> bool is_pattern(uint16_t x) { if (!x) return 0; for (; x > 0; x <<= 1) { if (!(x & 0x8000)) return false; } return true; } ``` 改寫上述程式碼,使其達到等價行為,但更精簡有效。 ```c bool is_pattern(uint16_t x) { const uint16_t n = EEEE; return (n ^ x) < FFFF; } ``` ### 想法與思考 先看看一開始的程式碼, x > 0 時將 x 向左位移1個,如果最高位元不是 1 return false,是 1 的話繼續迴圈,所以這個程式是從 MSB ( Most Significant Bit )檢查到 LSB ( Least Significant Bit )是否有連續的 1 。 了解程式邏輯後,就可以開始思考如何修改程式碼,n = ~x + 1,加 1 的目的將最右邊 1 的後面位元變得跟 x 一樣,將 n 跟 x 做 XOR,如果 MSB 沒有夾雜 0 的話, XOR出來的值必會小於 x,所以 `EEEE = ~x + 1 , FFFF = x` 。 :::success 這邊用 4-bits 做說明 x = 1100 n = 0011 + 1 = 0100 x ^ n = 1000 < x :::