---
tags: sysprog2020
---
# 2020q3 Homework2 (quiz2)
contributed by < `nickchenchj` >
## Prerequisite
* [problems for quiz2 (2020q3)](https://hackmd.io/@sysprog/2020-quiz2)
## Problem 1
### Required Functions
* Check if the input string is in ASCII
* Process 64 bits at once (8 chars)
### Code Snippets
* `is_ascii` (8-bit version)
```c=
#include <stdbool.h>
#include <stddef.h>
bool is_ascii(const char str[], size_t size)
{
if (size == 0)
return false;
for (int i = 0; i < size; i++)
if (str[i] & 0x80) /* i.e. (unsigned) str[i] >= 128 */
return false;
return true;
}
```
* `is_ascii` (64-bit version)
```c=
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
bool is_ascii(const char str[], size_t size)
{
if (size == 0)
return false;
int i = 0;
/* Processes 8 chars at once in each iteration */
while ((i + 8) <= size) {
uint64_t payload;
memcpy(&payload, str + i, 8);
if (payload & MMM)
return false;
i += 8;
}
/* Processes the remaining chars one by one */
while (i < size) {
if (str[i] & 0x80)
return false;
i++;
}
return true;
}
```
#### MMM = ?
* `(a)` `0x0080008000800080`
* `(b)` `0x8000800080008000`
* `(c)` `0x0808080808080808`
* `(d)` `0x8080808080808080`
* `(e)` `0x8888888888888888`
* `(f)` `0xFFFFFFFFFFFFFFFF`
### How `is_ascii` (8-bit version) works?
| Base | ASCII | Extended ASCII |
|:----:|:---------------------:|:---------------------:|
| 2 | 0000 0000 ~ 0111 1111 | 1000 0000 ~ 1111 1111 |
| 16 | 0x00 ~ 0x7F | 0x80 ~ 0xFF |
:::info
:bulb: From the table, we can find that the [MSB](https://en.wikipedia.org/wiki/Bit_numbering#Most_significant_bit) of the char in ASCII is always `0` (binary), and that of the char in extended ASCII is always `1` (binary).
:::
* to extract the value of the 8th bit, apply `str[i] & 0x80`:
| Type | str[i] | str[i] & 0x80 | Result |
|:--------------:|:---------:|:-------------:|:-------:|
| ASCII | 0100 0011 | ==0==000 0000 | `0` |
| extended ASCII | 1100 0011 | ==1==000 0000 | not `0` |
###### Note: `0100 0011` and `1100 0011` were just examples for this demonstration
* after filtering, all bit values except for the [MSB](https://en.wikipedia.org/wiki/Bit_numbering#Most_significant_bit) were set to `0`, and the value of MSB remained unchanged
* from the result, all we need to do is to check whether the result is `0` or not
* if the result is `0`, then the char is in ASCII
### How `is_ascii` (64-bit version) works?
* similarly, we can apply the same method to `is_ascii` (64-bit version)
* we simply extend `0x80` to `0x8080808080808080` and perform bitwise AND on the input string (8 chars at once) until the result is non-zero or less than 8 chars remain
* by doing so, if the result of bitwise AND is `0`, it means that the 8 chars are all in ASCII
* if there are less than 8 chars remain in the input string, process them with `is_ascii` (8-bit version)
:::success
Answer: `(d)` `0x8080808080808080`
:::
<!-- ### Why `memcpy`?
### Advanced Applications
#### Example 1
A function that can check if a string of known length contains letters
#### Example 2
A function that can check if an input string doesn't contain any invalid characters
-->
## Problem 2
:::info
打英文好累,我還是乖乖打中文
:::
### Required Functions
* 將十六進為表示的字元轉為數值 (case insensitive)
* e.g.
* `0` => 0
* `1` => 1
* `A`, `a` => 10
* `F`, `f` => 15
### Code Snippets
* `hexchar2val`
```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;
}
```
#### MASK = ?
* `(a)` `0x10`
* `(b)` `0x20`
* `(c)` `0x40`
* `(d)` `0x60`
* `(e)` `0x80`
#### AAA = ?
* `(a)` 3
* `(b)` 2
* `(c)` 1
* `(d)` 0
#### BBB = ?
* `(a)` 7
* `(b)` 6
* `(c)` 5
* `(d)` 4
### 解題思路
:::info
以下摘自 ASCII 表格
* `'0'`, `'1'`, `'2'`, …, `'9'` 對應到 `0x30`, `0x31`, `0x32`, … `0x39`
* `'a'`, `'b'`, `'c'`, …, `'f'` (小寫) 對應到 `0x61`, `0x62`, `0x63`, …, `0x66`
* `'A'`, `'B'`, `'C'`, …, `'F'` 對應到 `0x41`, `0x42`, `0x43`, …, `0x46`
:::
* 首先考慮數字 `'0'`~`'9'`, 僅需將 `0x3X` 的 `3` 過濾掉即可得到正確數值
* 再來考慮英文的大小寫,可以由上方提供的資訊看出 `'a'` 與 `'A'` 都是從 `0xX1` 開始,而我們期望 `'a'` 與 `'A'` 的值都變成 10,因此需要做的事情就是先將 `'a'` 與 `'A'` 轉換成 `0x01` 再加上 9 (`0000 1001`) 即可得到期望的數值 (適用於所有英文字元)
* 那麼要如何取得 9 (`0000 1001`) 呢? 這時需要運用 bitwise AND 的技巧得到英文字元的特徵,同時也必須過濾掉數字 `'0'`~`'9'` 的特徵 (`0x3X` 的 `3`)
| 字元 | 二進位表示 | 過濾後 (letter) |
|:-----:|:----------:|:---------------:|
| `'1'` | 0011 0001 | 0==0==00 0000 |
| `'a'` | 0110 0001 | 0==1==00 0000 |
| `'A'` | 0100 0001 | 0==1==00 0000 |
* 根據上面的表格來推斷,只要將字元和 `0100 0000` 做 bitwise AND 之後就可以萃取出左邊第二個 bit 的值,因此可以得知 `MASK` = `0x40` (`0100 0000`)
* 上面有提到,將英文字元轉換成值的時候需要加上 9 (`0000 1001`),這時只要運算 `shift = (letter >> 3) | (letter >> 6)` 就可獲得我們想要的值
| 字元 | shift |
|:------------------------:|:---------:|
| `'0'`~`'9'` | 0000 0000 |
| `'a'`~`'z'`, `'A'`~`'Z'` | 0000 1001 |
* 最後用 `(in + shift) & 0xf` 取得最小的 4 個位元就是答案了!
:::success
Answer:
MASK = `0x40`
AAA = 3
BBB = 6
:::
## Problem 3
### Required Functions
* 快速取餘數
### Code Snippets
* `quickmod`
```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;
}
```
#### XXX = ?
* `(a)` `0x00F000F000F00080`
* `(b)` `0xF000F000F000F000`
* `(c)` `0x0F0F0F0F0F0F0F0F`
* `(d)` `0xF0F0F0F0F0F0F0F0`
* `(e)` `0x8888888888888888`
* `(f)` `0xFFFFFFFFFFFFFFFF`
### 解題思路
* 從第 6 行可以看出 `((__uint128_t) M * n) >> 64` 是 `n / D` 的商 (`quotient`)
* 從上述的程式可以推導出:
$$
M \times n = quotient \times 2^{64} = \frac{n}{D} \times 2^{64}\\
\Rightarrow M = \frac{2^{64}}{D}
$$
* 但是 `uint64_t` 的上限只到 $2^{64} - 1$, 因此代入 $XXX = 2^{64} - 1$ 後再加 $1$
* 可得到 $M = \lfloor{\frac{2^{64} - 1}{D}}\rfloor + 1$
#### 驗證 $M = \lfloor{\frac{2^{64} - 1}{D}}\rfloor + 1$ 的正確性
* 已知:
$$
quotient = \lfloor{\frac{n}{D}}\rfloor
$$
* 且假設:
$$
2^{64} - 1 = aD + r; \text{ where 0} \leq \text{ r < D}
$$
* 證明下列式子成立:
$$
\lfloor{\frac{n}{D}}\rfloor
= \lfloor{\frac{(\lfloor{\frac{2^{64} - 1}{D}}\rfloor + 1) \times n}{2^{64}}}\rfloor
$$
* 推導過程:
$$
\lfloor{\frac{(\lfloor{\frac{2^{64} - 1}{D}}\rfloor + 1) \times n}{2^{64}}}\rfloor
= \lfloor{\frac{(a + 1) \times n}{2^{64}}}\rfloor
= \lfloor{\frac{\frac{(a + 1) \times D}{D} \times n}{2^{64}}}\rfloor
= \lfloor{\frac{\frac{(a + 1) \times D}{D} \times n}{aD + r + 1}}\rfloor
= \lfloor{\frac{\frac{aD + D}{D} \times n}{aD + r + 1}}\rfloor
$$
因為 $(aD + D) \ mod \ D = 0$, 所以可以將除數 $D$ 移到被除數 $n$ 底下:
$$
\lfloor{\frac{\frac{aD + D}{D} \times n}{aD + r + 1}}\rfloor
= \lfloor{\frac{aD + D}{aD + r + 1}}\rfloor \times \lfloor{\frac{n}{D}}\rfloor
$$
由於 $r + 1 \leq D$,因此:
$$
\frac{aD + D}{aD + r + 1} \geq 1
\Rightarrow \lfloor{\frac{aD + D}{aD + r + 1}}\rfloor = 1
$$
將上面結果代回推導式中:
$$
\lfloor{\frac{aD + D}{aD + r + 1}}\rfloor \times \lfloor{\frac{n}{D}}\rfloor
= 1 \times \lfloor{\frac{n}{D}}\rfloor
= \lfloor{\frac{n}{D}}\rfloor
= quotient
$$
* 證明完成!
:::success
Answer: `(f)` `0xFFFFFFFFFFFFFFFF`
:::
## Problem 4
### Required Functions
* 判斷是否能被整除
### Code Snippets
* `divisible`
```c=
bool divisible(uint32_t n)
{
return n * M <= YYY;
}
```
#### YYY = ?
* `(a)` `M + 1`
* `(b)` `M`
* `(c)` `M - 1`
* `(d)` `(M >> 1)`
* `(e)` `(M << 1)`
### 解題思路
* 延續上一題,已知 $M = \frac{0xFF...F}{3} + 1$,算出 $M = 0x5555\ 5555\ 5555\ 5555 + 1$
* 已知 $D = 3$,因此考慮 $n = 3k,3k + 1,3k + 2$ 三種情況
* $n = 3k:$
$M \times n = (0x555...5 + 1) \times 3k = (0xFFF...F + 3) \times k = 0x000...02 \times k = 2k$
* $n = 3k + 1:$
$M \times n = (0x555...5 + 1) \times (3k + 1) = (0x555...5 + 1) \times 3k + (0x555...5 + 1) = 2k + M$
* $n = 3k + 2:$
$M \times n = (0x555...5 + 1) \times (3k + 2) = (0x555...5 + 1) \times 3k + (0x555...5 + 1) \times 2 = 2k + 2M$
從上面結果可發現,只要 $n \times M < M$ 就代表 $n$ 可以被 $D$ 整除,因此可能的選項有 YYY = `M - 1` 或 `(M >> 1)`。如果考慮極端值 $n = 0xFFFF\ FFFF$,則 $k\_max = 0x5555\ 5555$
結果...還是沒有辦法剔除其中一個選項
:::success
Answer: `(c)` `M - 1` or `(d)` `(M >> 1)` (maybe)
:::
## Problem 5
### Required Functions
* 將指定的字串 (或部分字串) 的英文字母全改為小寫
### Code Snippets
* `strlower` (process one `char` in each iteration)
```c=
#include <ctype.h>
#include <stddef.h>
/* in-place implementation for converting all characters into lowercase. */
void strlower(char *s, size_t n)
{
for (size_t j = 0; j < n; j++) {
if (s[j] >= 'A' && s[j] <= 'Z')
s[j] ^= 1 << 5;
else if ((unsigned) s[j] >= '\x7f') /* extended ASCII */
s[j] = tolower(s[j]);
}
}
```
* `strlower_vector` (process 8 chars at once)
```c=
#include <ctype.h>
#include <stddef.h>
#include <stdint.h>
#define PACKED_BYTE(b) (((uint64_t)(b) & (0xff)) * 0x0101010101010101u)
/* vectorized implementation for in-place strlower */
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) { /* is ASCII? */
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);
}
```
#### VV1 = ?
* `(a)` `0x10`
* `(b)` `0x20`
* `(c)` `0x40`
* `(d)` `0x60`
* `(e)` `0x80`
#### VV2 = ?
* `(a)` `(-1)`
* `(b)` `0`
* `(c)` `1`
#### VV3 = ?
* `(a)` `(-1)`
* `(b)` `0`
* `(c)` `1`
#### VV4 = ?
* `(a)` `0x10`
* `(b)` `0x20`
* `(c)` `0x40`
* `(d)` `0x60`
* `(e)` `0x80`
### 解題思路
* `strlower_vector` 中的 `PACKED_BYTE(b)` 可以在 64 個 bit 中萃取出最小的 8 個 bit,然後將這 8 個 bit 複製到整個 64 bit
* 例如: `PACKED_BYTE(0x80)` => `0x8080 8080 8080 8080`
#### VV1
* VV1 沿用 [Problem 1](#Problem-1) 的技巧來判斷字串裡的字元是否都在 ASCII 表裡面,因此答案選擇 `0x80`
#### VV2, VV3
* 從 `strlower_vector` 的第 14, 15 行中,我們可以推斷它在判斷字串是否在 `'A'` 至 `'Z'` 的範圍裡
* `A`: 只要 `*chunk` 中的 字元都在 `'A'` 之後,那麼 `*chunk + PACKED_BYTE(128 - 'A' + VV2)` 的 8 的倍數的位元都會是 `1`
* `Z`: 只要 `*chunk` 中的 字元都在 `'Z'` 之前,那麼 `*chunk + PACKED_BYTE(128 - 'Z' + VV3)` 的 8 的倍數的位元都會是 `0`
| char | binary | `128 - 'A' + VV2` | `128 - 'Z' + VV3` | MSB_1 ^ MSB_2 |
|:-----:|:---------:|:-----------------:|:-----------------:|:-------------:|
| `' '` | 0010 0000 | ==0==101 1111 | ==0==100 0101 | 0 |
| `'A'` | 0100 0001 | ==1==000 0000 | ==0==110 0110 | 1 |
| `'Z'` | 0101 1010 | ==1==001 1001 | ==0==111 1111 | 1 |
| `'a'` | 0110 0001 | ==1==010 0000 | ==1==000 0110 | 0 |
* 為了達到上述結果,VV2 必須是 `0`,VV3 則必須是 `(-1)`
#### VV4
* 由於我們只需要 MSB (每 8 個 bit 取一個 MSB) 來判斷是否在範圍內,因此使用 `& 0x80` 來過濾掉非必要的資訊
:::success
Answer:
VV1 = `0x80`
VV2 = 0
VV3 = (-1)
VV4 = `0x80`
:::
## Problem 6
### Required Functions
* 找到只出現過一次的值
### Code Snippets
* `singleNumber`
```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;
}
```
#### KKK = ?
* `(a)` `higher`
* `(b)` `lower`
* `(c)` `~higher`
* `(d)` `~lower`
* `(e)` `0xFFFFFFFF`
#### JJJ = ?
* `(a)` `higher`
* `(b)` `lower`
* `(c)` `~higher`
* `(d)` `~lower`
* `(e)` `0xFFFFFFFF`
### 解題思維
我們需要 2 個變數 `higher` 和 `lower` 來表示 3 個狀態,分別是 `00`,`01`,`10`,並且根據當前的 `higher`,`lower` 和 `input` 的值來決定下一個 `higher` 和 `lower` 的值
* 以下的 `input` 只有一個 bit 的長度,但此概念也可套用在多個 bit 的情況
#### Transition
* $input\_bit = 1:$ `00` => `01` => `10` => `00` => ...
* $input\_bit = 0:$ 不改變狀態
* Transition table (overall):
| higher state | lower state | input bit | (higher state)' | (lower state)' |
|:------------:|:-----------:|:---------:|:---------------:|:--------------:|
| 0 | 0 | 1 | ==0== | ==1== |
| 0 | 1 | 1 | ==1== | ==0== |
| 1 | 0 | 1 | ==0== | ==0== |
| 0 | 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 0 | 1 |
| 1 | 0 | 0 | 1 | 0 |
#### `lower` transition:
* Transition table (`lower`):
| higher state | lower state | input bit | (lower state)' |
|:------------:|:-----------:|:---------:|:--------------:|
| 0 | 0 | 1 | ==1== |
| 0 | 1 | 1 | ==0== |
| 1 | 0 | 1 | ==0== |
| 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 |
| 1 | 0 | 0 | 0 |
* 如果 $input\_bit = 0$,`lower` 不做改變,如果 $input\_bit = 1$, `lower` 狀態會變化 (`0` => `1`; `1` => `0`),所以我們可以使用 $lower' = input\_bit \hat{} lower$ 來達到我們想要的變化
* 但是可以發現,當 $higher = 1$ 且 $input\_bit = 1$ 的時候, $lower'$ 會等於 `1`,並非我們所期望的 `0`。因此,我們也把 `higher` 加入考量
* 經過觀察,只有在 $higher = 1$ 的時候需要將 $input\_bit \hat{} lower$ 的值反轉 (`1` => `0`),所以我們選擇先反轉 `higher`,將其變成 `0` 之後再跟 $input\_bit \hat{} lower$ 進行 bitwise AND 運算
* 最終,我們得到:
$lower' = (input\_bit \hat{} lower) \& (\text{~} higher)$
* `input` 為多個 bit 時仍適用上方公式,因此可得:
$\Rightarrow lower' = (input \hat{} lower) \& (\text{~} higher)$
#### `higher` transition:
* Transition table (`higher`):
| higher state | (lower state)' | input bit | (higher state)' |
|:------------:|:--------------:|:---------:|:---------------:|
| 0 | ==1== | 1 | ==0== |
| 0 | ==0== | 1 | ==1== |
| 1 | ==0== | 1 | ==0== |
| 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 0 |
| 1 | 0 | 0 | 1 |
* 如果 $input\_bit = 0$,`higher` 不做改變,如果 $input\_bit = 1$, `higher` 狀態會變化 (`0` => `1`; `1` => `0`),所以我們可以使用 $higher' = input\_bit \hat{} higher$ 來達到我們想要的變化
* 但是可以發現,當 $lower' = 1$ 且 $input\_bit = 1$ 的時候, $higher'$ 會等於 `0`,並非我們所期望的 `1`。因此,我們也把 `lower'` 加入考量
* 經過觀察,只有在 $lower' = 1$ 的時候需要將 $input\_bit \hat{} higher$ 的值反轉 (`0` => `1`),所以我們選擇先反轉 `lower'`,將其變成 `0` 之後再跟 $input\_bit \hat{} higher$ 進行 bitwise AND 運算
* 最終,我們得到:
$higher' = (input\_bit \hat{} higher) \& (\text{~} lower)$
* `input` 為多個 bit 時仍適用上方公式,因此可得:
$\Rightarrow higher' = (input \hat{} higher) \& (\text{~} lower)$
:::warning
:warning: `lower` 已在 **lower transition** 中被更新過了,因此 $higher' = (input \hat{} higher) \& (\text{~} lower)$ 中的 `lower` 即代表 `(lower state)'`
:::
:::success
Answer:
KKK = `~higher`
JJJ = `~lower`
:::
<!-- ## References -->