# 2020q3 Homework2 (quiz2) contributed by < `carlhutant` > [TOC] ## 測驗 1 ```c= bool is_ascii(const char str[], size_t size) { if (size == 0) return false; int i = 0; while ((i + 8) <= size) { uint64_t payload; memcpy(&payload, str + i, 8); if (payload & MMM) return false; i += 8; } while (i < size) { if (str[i] & 0x80) return false; i++; } return true; } ``` char size 為 1 byte 若只使用 7 位元編碼 則須確保 MSB 為 0 因此跟 10000000(0x80) 做 and 即可 一次比對 64 位元時,將 8 個 char 合起來 因此將 8 個 0x80 合起來即可 `MMM = (d) 0x8080808080808080` ## 測驗 2 ```c= uint8_t hexchar2val(uint8_t in) { const uint8_t letter = in & MASK; const uint8_t shift = (letter >> AAA) | (letter >> BBB); return (in + shift) & 0xf; } ``` 第三行判斷 in 是否為字母 因為字母皆有 0x01X0 XXXX 的特徵 所以與 0x0100 0000 and 可得到 0x0100 0000 `MASK = (c) 0x40` 第五行的 & 將所有輸入高位的 byte 去除 從觀察可發現 0~1 剛好變成 0~1 a~f 與 A~F 都變成 1~6 全都加 9 就達到目標 因此 shift 為 0x0000 1001 可知第四行原本 letter 為 0x0100 0000 應該右移 3 與 6 `AAA = (a) 3` `BBB = (b) 6` ## 測驗 3 ```c= const uint32_t D = 3; #define M ((uint64_t)(UINT64_C(XXX) / (D) + 1)) /* compute (n mod d) given precomputed M */ uint32_t quickmod(uint32_t n) { uint64_t quotient = ((__uint128_t) M * n) >> 64; return n - quotient * D; } ``` 目標是要達到 $\dfrac{n}{D} = \dfrac{X}{D} \times \dfrac{n}{X}$ $\dfrac{X}{D}$ 可事先算好並重複使用, $\dfrac{n}{X}$ 可透過 shift 就達到 因此只須做乘法計算 從第六行可知 X 應該為 $2^{64}$ 但選項中沒有,而且第二行最後還做了加一的行為 那我們就先設 XXX 為 $2^{64}-x$ 第六行會變成 : ```c=6 uint64_t quotient = ((__uint128_t) ((uint64_t)(UINT64_C(XXX) / (D) + 1)) * n) >> 64; ``` (UINT64_C(XXX) / (D) + 1)) 先計算,結果為 $\left\lfloor{\dfrac{2^{64}-x}{D}} \right\rfloor+1$ ,為整數,因為是 int 運算,小數捨去 也可表示成 $\dfrac{2^{64}-x-r}{D}+1$ ,也為整數, 其中 $r$ 為 $2^{64}-x$ 除以 D 的餘數 接著乘 n ,結果為 $\dfrac{n\times2^{64}-nx-nr}{D}+n$ ,為整數 接著向右 shift 64 bits 可視為除以 $2^{64}$ 後捨去小數 結果為 $\left\lfloor\dfrac{n}{D}-\dfrac{nx}{D\times2^{64}}-\dfrac{nr}{D\times2^{64}}+\dfrac{n}{2^{64}}\right\rfloor=\left\lfloor\dfrac{n}{D}+\dfrac{\dfrac{-nx-nr+nD}{2^{64}}}{D}\right\rfloor$ 因為我們希望最終結果等於$\left\lfloor\dfrac{n}{D}\right\rfloor$ 所以必須確保 $-R<=\dfrac{n(-x-r+D)}{2^{64}}<D-R$ ,其中 $R$ 為 $n$ 除以 $D$ 的餘數 由題目可知 : 1. $n$ 為 32 bits uint 2. $x$ 由選項 A~F 決定,可為 0x0~0xFFFFFFFFFFFFFFFF 的 64 bits uint 3. $r$ 為 $2^{64}-x$ 除以 D 的餘數,範圍在 0~D-1 的 32 bits uint 4. $D$ 為 32 bits uint 發現可透過 $x$ 將 $(-x-r+D)$ 限制在 $2^{32}$ 以內的正數 兩個 32 bits uint 相乘不會超過 64bits 達到 $-R<=0<=\dfrac{n(-x-r+D)}{2^{64}}<1<=D-R$ $D-r$ 的範圍在 $2^{32}$~$1$ ( $2^{32}$ 不確定,但最小是 1 ),所以 $x$ 就只能是 0 或 1 了 `XXX = (f) 0xFFFFFFFFFFFFFFFF` 推測使用 0xFFFFFFFFFFFFFFFF 而非 0xFFFFFFFFFFFFFFFF + 1 是為了記憶體操作方便 ## 測驗 4 ```c= bool divisible(uint32_t n) { return n * M <= YYY; } ``` 此設計的核心建立在 uint64_t 乘法發生的 overflow 上 $D \times (\left\lfloor{\dfrac{2^{64}-1}{D}} \right\rfloor+1)$ 可能為 $2^{64}-1+D$ 或是 $2^{64}-1-r+D$ 其中 $r$ 為 $2^{64}-1$ 除以 D 的餘數 以上兩者皆大於 0xFFFFFFFFFFFFFFFF 因此會 overflow ,在 uint64_t 表示下會是 $D$ 或是 $D-r$ 接著 $n$ 可以拆解為 $t \times D + s$ ,其中 $t$ 與 $s$ 皆為非負整數, $s$ 小於 $D$ $t \times D \times M$ 會 overflow ,在 uint64_t 下是 $t \times D$ 或是 $t \times (D-r)$ $s$ 最大為 $D-1$ 乘上 $M$ 可能為以下兩者 : 1. $\dfrac{2^{64}-1}{D}$ 為整數 -> $2^{64}-1+D-(\dfrac{2^{64}-1}{D}+1)$ 2. $\dfrac{2^{64}-1}{D}$ 不為整數 -> $2^{64}-1-r+D-(\dfrac{2^{64}-1-r}{D}+1)$ 因為 $D$ 為 32 bits uint , 0xFFFFFFFFFFFFFFFF 除以任何 32 bits uint 的商皆會大於除數,也就是分數部分大於 $D$ ,第二種雖然有 $-r$ 但只會使其整除不會改變整數部分 因此兩者都必小於 $2^{64}$ 也就是不會 overflow ,所以只要 $s>0$ 結果必定大於 $M$ 此設計就是 $n$ 拆解為 $t \times D + s$ ,只要 $s>0$, $n \times M$ 就至少為 $M$ ,只要 $s=0$, $n \times M$ 就因為 overflow 小於 $M$ ( 或是 $t=0$ ) 但如何確保 $s>0$ 時,$s \times M$ 不會因為加上 $t \times D \times M$ 的結果而 overflow 變成小於 $M$ ? 首先 $t \times D \times M$ 在 uint64_t 下為 $t \times D$ 或是 $t \times (D-r)$ 皆小於 $2^{32}$ $$ \begin{align} s \times M &\leq (D-1)\times M\\ &=(D-1)(\dfrac{2^{64}-1-r}{D}+1)\\ &\leq\frac{(D-1)2^{64}}{D}\\ &=2^{64}(1-\dfrac{1}{D})\\ &<2^{64}(1-\dfrac{1}{2^{32}})\\ &=2^{64}-2^{32} \end{align} $$ 則 $(t \times D) + (s \times M) < (2^{32})+(2^{64}-2^{32})$ 不會 overflow ,也就是會大於等於 $M$ `YYY = (c) M - 1` ## 測驗 5 ```c= #include <ctype.h> #include <stddef.h> #include <stdint.h> #define PACKED_BYTE(b) (((uint64_t)(b) & (0xff)) * 0x0101010101010101u) void strlower_vector(char *s, size_t n) { size_t k = n / 8; for (size_t i = 0; i < k; i++, s += 8) { uint64_t *chunk = (uint64_t *) s; if ((*chunk & PACKED_BYTE(VV1)) == 0) { uint64_t A = *chunk + PACKED_BYTE(128 - 'A' + VV2); uint64_t Z = *chunk + PACKED_BYTE(128 - 'Z' + VV3); uint64_t mask = ((A ^ Z) & PACKED_BYTE(VV4)) >> 2; *chunk ^= mask; } else strlower(s, 8); } k = n % 8; if (k) strlower(s, k); } ``` 程式要做的就只有 1.找出 A~Z 的字元 2.將該字元第 6 個 bit 設為 1 但為了要加速,避免使用 if else 判斷式,並且一次處理 8 個 char 真正改變 bits 的位置在第 16 行,而且從使用 exclusive or 可知道 mask 一定會根據對應的 char 不同而變化,若對應 A~Z 為 0x20 否則為 0x00 在往上看到 `mask = ((A ^ Z) & PACKED_BYTE(VV4)) >> 2` 可知 `(A ^ Z) & PACKED_BYTE(VV4)` 會是由 0x80 或 0x00 共八個組成 那`PACKED_BYTE(VV4)` 就可能是 0x8080808080808080 並且由 `(A ^ Z)` 對應的 bits 來決定要保留還是改成 0x00 `VV4 = (e) 0x80` 可知當 char 位於 A~Z 時,A 與 Z 該位置的 MSB 彼此會不同 看到 13 14 行, `128 - 'A'` 與 `128 - 'Z'` 為字元到 0x80 的差,此字元以上的 char 加此值 MSB 都會為 1 ,但我們要的是一個 1 一個 0 ,因為選項只有 1 0 -1 所以選擇 "大於等於 A 的 MSB 為 1" 與 "小於 Z+1 的 MSB 為 0" `VV2 = (b) 0` `VV3 = (a) (-1)` 但是要避免加上 A 與 Z 後超出 0xff 影響其他 char ,必須將每個 char 的值限制在 0x7f 以下,也就是 MSB 為 0 ,因此在第 12 行將有大於 0x7f 的資料 8 筆一起交由 strlower 處理 `VV1 = (e) 0x80` ## 測驗 6 ```c= int singleNumber(int *nums, int numsSize) { int lower = 0, higher = 0; for (int i = 0; i < numsSize; i++) { lower ^= nums[i]; lower &= KKK; higher ^= nums[i]; higher &= JJJ; } return lower; } ``` 當ㄧ個數與其他 xor 在一起後可再次 xor 相同的其他數還原出此數 可想成第一次 xor 會將數字的 information 加入,第二次則會移除 本設計為第一次出現的資訊會出現在 lower 第二次會從 lower 中移除但加入 higher 中 第三次會從 higher 中移除 `KKK = ~higher` `JJJ = ~lower`