# 2020q3 Homework2 (quiz2)
## 測驗一
考慮到下列程式碼,必須要一次判斷 64 位元的資料
```clike=
#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;
}
```
所以要判斷每個 char 是不是 >= 128 ,也就是針對每個 char 做 `str[i] & 0x80` 。
而每個char又是 1 byte ,因此一個 uint64_t 可以塞下 8 個 char ,因此 MMM 為八個 `0x80` 也就是 `0x8080808080808080`
### 測驗一延伸問題
1. 為何使用 `memcpy` ?
考慮到 `str[i]` 可能不是對齊,在 bit wise 操作上會需要載入兩次,所以先宣告一個確定對齊的空間,再做比對比較好。
## 測驗二
考慮到 `0xa` ~ `0xf` 與 `0xA` ~ `0xF` 判斷上是一樣的數字,但是要與 `0x0` ~ `0x9` 不一樣,因此觀察兩者的二進位
```
'1' = 0011 0001
'a' = 0110 0001
'A' = 0100 0001
```
觀察到英文的都是 01?0 ,而數字的則是 00?1 ,因此可以藉由第 2 bit 或第 4 bit 來判斷
接著看到下方的程式碼
```clike=
const uint8_t shift = (letter >> AAA) | (letter >> BBB);
return (in + shift) & 0xf;
```
可以判斷是英文的部分要用shift的方式讓他 +10 ,所以知道上方的判斷的是英文字,也就是第 2 bit 的部分,因此 `MASK = 0x40`
而接下來要用 shift 的方式讓其後面 4 bit 可以加十,觀察其二進位
```
0x40 = 0100 0000
10 = 0000 1001 + 0000 0001
```
因此需要 shift 3 bit 與 shift 6 bit ,才可以讓 `a` 或是 `A` 變成 10 。因此 AAA 與 BBB 分別為 3 與 6 。
### 測驗二延伸問題
1. 解釋上述程式的運作原理
上面的程式碼需要先判斷是不是英文,如果是英文則將其後四位 +9 ,若不是則加 0 ,最後取後 4 bit 並且回傳。
## 測驗三
考慮以下程式碼:
```clike=
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;
}
```
除法的算法為 ${n \over d}=n * {{2^N \over d} \over 2^N}$
而在這裡是除以 $2^{64}$ ,也就是 64 bit
因此 M 就是 ${2^N \over d}$ ,但是 $2^{64}$ 會超過 uint64_t 的大小,因此必須要使用 ${{2^N -1} \over d}+1$ ,所以 XXX 就是 $2^N-1$ 也就是 `0xffffffffffffffff`
### 測驗三延伸題目
1. 解釋上述程式的原理
## 測驗四
考慮以下程式碼:
```clike=
bool divisible(uint32_t n)
{
return n * M <= YYY;
}
```
如果 $n \over d$ 有小數的部分,則他不能被整除。
我們要判斷知道 ${2^N \over d}*n$ 是不是不大於 YYY
我們已經知道數字大小就是 64 bit ,因此將 $n \over d$ 乘以 $2^64$ 就是只取其小數的部分
但是如果小數部分小於 $1 \over {2^{64}}$ ,則可以判斷他沒有餘數,所以就是可以判斷它可以被整除。
## 測驗五
PACKED_BYTE(b) 是 (((uint64_t)(b) & (0xff)) * 0x0101010101010101u ,這代表把 b 只取最後一個 byte 並且複製 8 個到 uint64_t
所以 VV1 要判斷 8 個 byte 是不是在 ascii 內,所以就是每個 byte 是不是在 128 的範圍內,因此 VV1 是 `0x80`
接著觀察大小寫的二進位:
```
a for 0110 0001
A for 0100 0001
128 - A 0011 1111
128 - Z 0010 0110
```
也就是說,如果要換成小寫的話,就是把 A ~ Z 範圍內的字元的第三 bit 換成 1
所以將 chunk 加上 128 與 'A' 的距離成為結果 A ,以及 128 與 'Z' + 1 的距離成為結果 Z ,
如果 A 超過 128 但是 Z 沒超過 128 ,則可以判斷他是在 A ~ Z 的範圍內。
所以兩者 XOR 後會出現 `1xxx xxxx` ,將他只取第一 bit 並且 >> 2 後,與原本的 chuck 進行 XOR ,
就可以將大寫換成小寫(因為將第三 bit 換成 1 了)
因此可以判斷 VV2 為零, VV3 為 -1 (因為 128 - ('Z' + 1) = 128 - 'Z' - 1), VV4 為 `0x80` (因為只取第一 bit)
### 測驗五延伸問題
1. 解釋上述程式的原理
## 測驗六
題目要判斷出現一次的數字,其他數字都是出現三次
考慮到任意數 XOR 同一個數字兩次,會變成原本的數字