# 2020q3 第 2 週測驗題
## 測驗1
- ### 運作原理:
```cpp=
#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;
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;
}
```
因為逐一比對字元的效率比較低,所以本題題目使用```memcpy()```把8個字元存到同一個變數中,並使用```& MMM```去判斷是否為ASCII code,而ASCII code的正常範圍在```0x00```~```0x7F```之間,若要判斷是否為ASCII code,只要判斷其數值小於```0x80```即可,所以每個字元去```& 0x80```若答案大於 0 則代表該字元的MSB等於 1 ,也代表其數值大於等於```0x80```即可回傳```false```。
- ### 判斷輸入是否為有效英文字元
```cpp=
bool is_ch(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);
payload |= 0x2020202020202020; //turn to lower case
if(!(payload) & 0x4040404040404040){
return false;
}else{
payload &= 0x1F1F1F1F1F1F1F1F; //only final 5 bits
bool smaller_than_a = (payload - 0x0101010101010101) & ~(payload) & 0x8080808080808080;
bool bigger_than_z = ~(payload - 0x1B1B1B1B1B1B1B1B) & 0x8080808080808080;
if(smaller_than_a || bigger_than_z){
return false;
}
}
i += 8;
}
while(i < size){
uint8_t payload;
memcpy(&payload, str + i, 1);
payload |= 0x20;
if(!(payload & 0x40)){
return false;
}else{
payload &= 0x1F;
bool smaller_than_a = (payload - 0x01) & ~(payload) & 0x80;
bool bigger_than_z = ~(payload - 0x1B) & 0x80;
if(smaller_than_a || bigger_than_z){
return false;
}
}
i++;
}
return true;
}
```
1.把由左數來第三個bit改成1(若該字元為大寫英文轉成小寫英文)。
2.檢查由左數來第二個bit是否為1(此為英文字母ASCII code的特徵)。
3.smaller_than_a參考[bitwise操作](https://hackmd.io/@sysprog/c-bitwise)去判斷比```'a'```還要小(只剩```0x00```這個值)的狀況。
4.bigger_than_z去判斷扣掉```0x1B```(比```'z'```多1)後,是否有overflow,如果有則比```z```還要小。
## 測驗2
- ### 運作原理
```cpp=
uint8_t hexchar2val(uint8_t in)
{
const uint8_t letter = in & MASK;
const uint8_t shift = (letter >> AAA) | (letter >> BBB);
return (in + shift) & 0xf;
}
```
第3行希望把英文跟數字分開,觀察下來,發現英文跟數字的差別如下:
```graphviz
graph G{
rankdir=TB
node[shape=record]
A[label = "數字|0011 xxxx"]
B[label = "英文|01x0 xxxx"]
}
```
所以只要判斷左邊數來第二個或第四個bit就可以了,所以若```letter = 0```時為數字時```mask = 0x 40```,若```letter = 0```時為英文時```mask = 0x10```。
但看最後一行的```return (in + shift ) & 0xf```可以看出,如果```in```為數字,且令```shift = 0```,```return```為```in```,所以可以猜測```mask = 0x40```。
如果要把英文轉為數字,觀察規律可以發現只要+9就可以了,所以這第四行這邊只是在湊數字,讓```shift = 9```就可以了。
結論:如果是數字,直接輸出就好,如果是英文,要+9再輸出。
## 測驗3
- ### 運作原理
```cpp=
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;
}
```
$M = \dfrac{XXX}{D} + 1$
$quotient = M * n * \dfrac{1}{2^{64}}$
$return = n - quotient * D$
先計算 $M$:
$M = \dfrac{XXX}{D} + 1$
由題目指示與第6行後面的```>>64```可以推測$XXX$與 $2^{64}$ 接近,但因為$XXX$為 64bits,如果直接存 $2^{64}$ 會overflow,所以
$XXX = 0xFFFFFFFFFFFFFFFF$
下一行
$quotient = M * n * \dfrac{1}{2^{64}}$
代入$M$
$quotient = (\dfrac{2^{64}-1}{D} + 1) * n * \dfrac{1}{2^{64}}$
化簡
$quotient = \dfrac{n}{D} + \dfrac{n(D-1)}{2^{64}*D}$
此時如果要獲得餘數,只要$n - \dfrac{n}{D}$即可,而如果$quotient$後面那項只要$< 1$,即不能被```unsigned integer```儲存,所以證明$\dfrac{n(D-1)}{2^{64}*D}<1$
化簡:
$n - \dfrac{n}{D} < 2^{64}$
又 $n < 2^{32}$ , $D>=1$
所以 $n - \dfrac{n}{D} < 2^{64}$ 成立。
## 測驗4
- ### 運作原理
```cpp=
bool divisible(uint32_t n)
{
return n * M <= YYY;
}
```
代入$M*n$
$M * n = \dfrac{n}{D} (2^{64} - 1) + n$
令 $T = (2^{64}-1)$,$M * n = n(\dfrac{1}{D} * T + 1)$
先看前面部份$\dfrac{n}{D}(2^{64} - 1)$,如果整除,因為變數的type為```uint64_t```所以 $2^{64}$ 不論乘哪個整數,都會因為overflow而直接變成 $0$ ,減去商一次overflow相等於 $2^{64} - \dfrac{n}{D}$,在加上後面部份的 $n$ ,且 $n >= \dfrac{n}{D}$,所以又會在overflow一次,在整除狀況下 $M * n = n - \dfrac{n}{D} = n(1 - \dfrac{1}{D})$ ,而在variable type的限制下,$M *n$ 的最小值發生在 $n = 0$ ,此時 $M * n = 0$;最大值發生在 $n = 2^{32} - 1$ ,此時 $M * n = \dfrac{(2^{32}-1)(D-1)}{D}$,得在整除時:
$0 <= M *n <= \dfrac{(2^{32}-1)(D-1)}{D}$
而在非整除的狀況下,$M * n$ 的最小值發生在 $n = 1$, 此時 $M * n = \dfrac{2^{64}-1}{D}+1$;最大值發生在 $n = 2^{32} - 2$ , $D = 2^{32} - 1$ ,此時 $M * n = 2^{64} - 4$ ,得在非整除時:
$2^{32} + 2 <= M * n <= 2^{64} - 4$
## 測驗5
- ### 運作原理
```cpp=
#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);
}
```