# 2020q3 Homework2 (quiz2)
contributed by < `ignite1771` >
## 1
因為一次要檢查 64-bits, 所以就去 `&` 0x0080 四組
:::danger
錯了,正確答案是 `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;
}
```
### 推論過程
1. 對於大小寫字母而言:
因為 `'A'` 和 `'a'` 其 ASCII 值對應為 `0x61`, `0x41`, 而 `hexchar2val('A')` 和 `hexchar2val('a')` 皆為 10, 觀察程式返回值 `return (in + shift) & 0xf;` 可得到:
- shift = 9, 25 (9 + 16), 41 (9 + 16 + 16)... 即 9 + 16k, where k 是 >= 0 之整數
- 以 0x09 為例子,即 `00001001`,又觀察 `const uint8_t shift = (letter >> AAA) | (letter >> BBB);` ,得知需要得到 2 個數值 `00001000` 和 `00000001` 來去做 OR 運算:
- 假設 Mask 是 `0x10`, 則大小寫英文字母做完 AND 運算後皆為 0, 故不可能
- 假設 Mask 是 `0x20`, 則大小寫英文字母做完 AND 運算後皆為 `0x20`
- 對應的 AAA = 2, BBB = 5
- 假設 Mask 是 `0x40`, 則大小寫英文字母做完 AND 運算後皆為 `0x40`
- 對應的 AAA = 3, BBB = 6
- 假設 Mask 是 `0x60`, 則大寫字母做完 AND 運算後為 `0x60`, 小寫字母則為 `0x40`
- 大寫不可能產生上述 2 個數值,所以不對
- 假設 Mask 是 `0x80`, 則大小寫英文字母做完 AND 運算後皆為 0, 故不可能
2. 因此,我們推斷 Mask 有兩種可能,接下來分別將其判斷數字 `'0'` ~ `'9'` 的部分:
- 假設 Mask 是 `0x20`, 則數字做完 AND 運算後皆為 `0x20`
- 與大小寫字母結果相同,所以不對
- 假設 Mask 是 `0x40`, 則數字做完 AND 運算後皆為 `0x00`
- 因此就是看其輸入 `in` 本身的 LSB, 對了! :white_check_mark:
答案: Mask = `0x40`, AAA = `3`, BBB = `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;
}
```
首先觀察 macro `M` 裡面有個 `UINT64_C()`,一開始原本以為是 primitive type,後來看他是大寫命名並有括號才猜測是一個 macro, 查閱 C99 規格書 7.18.4.1
> **7.18.4.1 Macros for minimum-width integer constants**
The macro INT**N**_C(value) shall expand to an integer constant expression corresponding to the type int_least**N**_t. The macro UINT**N**_C(value) shall expand to an integer constant expression corresponding to the type uint_least**N**_t. For example, if uint_least**64**_t is a name for the type unsigned long long int, then UINT**64**_C(0x123) might expand to the integer constant 0x123ULL.
在 [stackoverflow: What does the following C macro do?](https://stackoverflow.com/questions/16782442/what-does-the-following-c-macro-do) 即可更加確認:
```c
#ifndef INT64_C
#define INT64_C(c) (c ## LL)
#define UINT64_C(c) (c ## ULL)
#endif
```
`##` 為串接起來 token pasting operator, 且可以在 Preprocess 階段來做,而 `strcat` 函式則是只能在 Run-time 階段
- 此 macro 為讓其整數 LL, ULL 更 readable 的一種表示法。
:::warning
經由 try-and-error 知道 XXX = `0xFFFFFFFFFFFFFFFF` (如果是 uint64_t, 則 = $2^{64} - 1$), 但為什麼呢?
:::
觀察題目,當我們把 $\dfrac{n}{d}$ 分子分母同乘以 $2^N$ 時會變成 $n \times \dfrac{\dfrac{2^N}{d}}{2^N}$ , 看似我們可以令 $M = \dfrac{2^N}{d}$, 而程式的 macro 定義的算式為 $M = \lfloor{\dfrac{2^N - 1}{d}\rfloor + 1}$, 為什麼呢?
從程式碼第 6, 7 行得知 M 最終應該得到 $\lfloor\dfrac{2^N}{d}\rfloor$, 以致可以 return 餘數 `n - quotient * D`
- 那麼 $\lfloor{\dfrac{2^N - 1}{d}\rfloor + 1}$ 會等於 $\lfloor\dfrac{2^N}{d}\rfloor$ ?
參考 [維基百科 Divison by a constant](https://en.wikipedia.org/wiki/Division_algorithm) 的敘述,得知上方 + 1 的部分應是做 round up, 所以結果會相等。
## 4
查閱 [C Operator Precedence](https://en.cppreference.com/w/c/language/operator_precedence), 知道優先順序 `* / %` > `+ -` > `>> <<` > `>= <=` (relational) > ... > ... > `<<= >>= =`
n * M = $n \times \lfloor\dfrac{2^N}{d}\rfloor$
因為上一題餘數 = `n - quotient * D`, $D = 3$, 又 quotient = (n * M) >> 64, 即 n * m = quotient << 64 = $quotient \times 2^{64}$, 而要整除的條件是 $n = quotient \times 3$, `n * M <= YYY` 做移項得到 `n * M - YYY <= 0`
:::warning
經由 try-and-error 知道 YYY = `M`, 但為什麼呢?
:::
:::danger
錯了,正確答案是 `M - 1`
:::
## 5
- VV1:
首先 `* 0x0101010101010101u` 會讓 `((uint64_t)(b) & (0xff))` 之值複製成為 64 bits, 又 `uint64_t *chunk = (uint64_t *) s;` 讓 `*chunk` 一次指到 64 bits char, 要判斷是不是 ASCII 字元,即判斷一個 byte 是否 <= `0x7f`, 所以 VV1 = `0x80` 如果 AND 運算後不是 0, 那就不是 ASCII 字元
:::warning
以下根據猜測來進行 try-and-error
:::
- VV2, VV3:
因為 `'A'`, `'Z'` 是大寫字母的頭跟尾,而 128 是 0x80 > 0x7f, 推測應該是與超過 ASCII 字元能表示範圍有關係,所以跟距離有關係,憑經驗猜測 VV2, VV3 應該是要加回 `1`
:::warning
錯了,正確答案是 VV2 = `0`, VV3 = `-1`
:::
- VV4:
因為 VV1 = `0x80`, 所以猜測 VV4 也跟 ASCII 表示範圍之類的相關,所以猜測應該也是 `0x80`
## 6
首先,對於 KKK 選項 (`lower &= KKK`) 推測有一些不可能:
- (a) higher
- (b) ~~lower~~ (lower 值不變,冗)
- \(c\) ~higher
- (d) ~~~lower~~ (AND 自己的反位元就變 0 了,要幹嘛?)
- (e) ~~0xFFFFFFFF~~ (lower 值不變,冗)
因為直接 XOR 對方似乎還缺點什麼,那我們就猜測是 XOR 對方的反位元,即 \(c\) ~higher
我們可以印出每一回合內 lower, higher 經 bit-wise 運算之改變:
```c
int singleNumber(int *nums, int numsSize)
{
int lower = 0, higher = 0;
for (int i = 0; i < numsSize; i++) {
printf("i = %d\n", i);
lower ^= nums[i];
printf("lower = %#.8x\n", lower);
lower &= ~higher; // KKK
printf("lower = %#.8x\n", lower);
higher ^= nums[i];
printf("higher = %#.8x\n", higher);
higher &= ~lower; // JJJ
printf("higher = %#.8x\n", higher);
printf("Round i = 0: lower = %d, higher = %d\n", lower, higher);
printf("=================\n");
}
return lower;
}
```
```
/* Sample input */
[2, 2, 3, 2]
/* Sample output */
i = 0
lower = 0x00000002
lower = 0x00000002
higher = 0x00000002
higher = 00000000
Round i = 0: lower = 2, higher = 0
=================
i = 1
lower = 00000000
lower = 00000000
higher = 0x00000002
higher = 0x00000002
Round i = 0: lower = 0, higher = 2
=================
i = 2
lower = 0x00000003
lower = 0x00000001
higher = 0x00000001
higher = 00000000
Round i = 0: lower = 1, higher = 0
=================
i = 3
lower = 0x00000003
lower = 0x00000003
higher = 0x00000002
higher = 00000000
Round i = 0: lower = 3, higher = 0
=================
```
雖然得到了答案,但對於其中運算原理還是不理解,故去看了 [Leetcode 討論區此篇留言](https://leetcode.com/problems/single-number-ii/discuss/43294/Challenge-me-thx/176039) ,得知 `lower ^= num[i]` 和 `higher ^= num[i]` 之 XOR 運算若把其看成是 num[i] 有沒有在該 Set 的話, 那麼會 `(lower ^ num[i]) & ~higher` 有以下的邏輯:
```python
IF the set "lower" does not have A[i]
Add A[i] to the set "lower" iff it is not in set "higher"
ELSE
Remove it from the set "lower"
```
出現第一次的會被放入 lower 此 set, 然後出現第二次會從 lower 此 set 被移出。
同理,`(higher ^ num[i]) & ~lower` 運算代表,出現第二次的會被放入 higher 此 set, 然後出現第三次會從 higher 此 set 被移出,如此可知道最後出現在 lower 此 set 的一定是只出現 1 次的 num.
答案: KKK = `~higher`, JJJ = `~lower`