# 2020q3 Homework2 (quiz2)
contributed by < `quantabase13` >
# :memo: Table of Contents
[TOC]
---
## 程式運作原理
### 測驗 1
```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;
while ((i + 8) <= size) {
uint64_t payload;
memcpy(&payload, str + i, 8);
if (payload & 0x8080808080808080/*MMM*/)
return false;
i += 8;
}
while (i < size) {
if (str[i] & 0x80)
return false;
i++;
}
return true;
}
```
* ASCII code 共有128個字元,以7位 bit 進行編碼 ; 另外, char 類的大小為8個 bits。所以若要確定一個 char 是否為 ASCII code ,只要確認其最高位是否為1即可。
* `is_ascii` 的 `while` loop 裡聲明了長度為64 bits 的 `payload`,並且透過 `memcpy` 從地址 `str + i` 複製8個 bytes 到 payload。由於一個 `char` 長1個 byte,`payload` 裝有8個 bytes,因此對 `payload` 做 bitwise operation 可以一次檢測 8 個 char 是否屬於 ASCII code。
* 當剩餘的 char 數量 < 8 時(也就是 i + 8 > size),就換成一次檢查1個 byte。
### 測驗 2
```c=
uint8_t hexchar2val(uint8_t in)
{
const uint8_t letter = in & 0x40/*MASK*/;
const uint8_t shift = (letter >> 3/*AAA*/) | (letter >> 6 /*BBB*/);
return (in + shift) & 0xf;
}
```
數字的 ASCII code 高4位為0011,大寫英文字母的高4位為 0100, 小寫英文字母的高4位為 0110。
於是取 `MASk` 為0x40的話,`letter` 在 `in` 為字母時數值為 0100 ,`in` 為數字時數值為 0。另外,不論大小寫字母,低四位皆是從0001開始遞增,因此要輸出其代表的十進位數值的話,只要將低四位+9即可(因為 a 表示十進位的10)。
整體程式碼的思路如下:
1. 計算 `shift` 值(0 => `in`為數字, 9 => `in` 為字母)
2. 取低四位 + `shift` 的值
### 測驗 3
```c=
const uint32_t D = 3;
#define M ((uint64_t)(UINT64_C(0xFFFFFFFFFFFFFFFF/*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;
}
```
此題參考 [jemalloc](https://github.com/jemalloc/jemalloc) 原始碼中 [include/jemalloc/internal/div.h](https://github.com/jemalloc/jemalloc/blob/dev/include/jemalloc/internal/div.h)和[src/div.c](https://github.com/jemalloc/jemalloc/blob/dev/src/div.c) 的註解,可以知道實現的思路(以下的除法是實際除法,不是 C 的 rounding 版本):
$$
\begin{aligned}\lfloor{(
\lceil\dfrac{2^k}{d}\rceil\cdot{\dfrac{n}{2^k}}})\rfloor&=\lfloor{(
\dfrac{2^k+r}{d}\cdot{\dfrac{n}{2^k}}})\rfloor\\&=\lfloor{(
\dfrac{2^k\cdot{n}}{d\cdot{2^k}}})+{(
\dfrac{r\cdot{n}}{d\cdot{2^k}}})\rfloor\\&=\lfloor{(
\dfrac{{n}}{d}})+{(
\dfrac{r\cdot{n}}{d\cdot{2^k}}})\rfloor\\&={\frac{n}{d}}+{\lfloor(
\dfrac{r\cdot{n}}{d\cdot{2^k}}})\rfloor
\end{aligned}
$$
其中
${r} = -({2^k})\space{mod}\space{d}\space,{n}= q\cdot{d}$。
由於${r}<{q}$ , 要讓最後的 flooring function 為 0 只要使$\dfrac{n}{2^k}\space < 1$ 即可(原始碼取 k = 32),就能達成利用$\lfloor{(
\lceil\dfrac{2^k}{d}\rceil\cdot{\dfrac{n}{2^k}}})\rfloor$ 計算 $\dfrac{n}{d}$ 的目標。
了解上述數學式後,就可以對此題的程式碼作相同的數學分析:
此處
$$
\begin{aligned}\lfloor{(
\lceil\dfrac{2^k-1}{d}\rceil\cdot{\dfrac{n}{2^k}}})\rfloor
&=\lfloor{(
\dfrac{2^k+{r}\prime}{d}\cdot{\dfrac{n}{2^k}}})\rfloor\\
&=\lfloor{(
\dfrac{2^k\cdot{n}}{d\cdot{2^k}}})+{(
\dfrac{{r\prime}\cdot{n}}{d\cdot{2^k}}})\rfloor\\
&=\lfloor{(
\dfrac{{n}}{d}})+{(
\dfrac{{r\prime}\cdot{n}}{d\cdot{2^k}}})\rfloor\\
&=\lfloor{(
\dfrac{{n-t}}{d}})+{\dfrac{t}{d}}+{(
\dfrac{{r\prime}\cdot{n}}{d\cdot{2^k}}})\rfloor\\
&={\frac{n-t}{d}}+{\lfloor(
{\dfrac{t}{d}}+\dfrac{{r\prime}\cdot{n}}{d\cdot{2^k}}})\rfloor
\end{aligned}
$$
其中
${r\prime} = 1+(-({2^k})\space{mod}\space{d})\space,{\dfrac{n-t}{d}}= \lfloor{\dfrac{n}{d}}\rfloor$ ,$t < d$。
我們只需讓${(
{\dfrac{t}{d}}+\dfrac{{r\prime}\cdot{n}}{d\cdot{2^k}}})$ < 1 即可。
固定$k = 64$,分析邊界情況。
當$\dfrac{r\prime}{d} = 1$ , $\dfrac{n}{2^k}=\dfrac{2^{32}-1}{2^{64}}$(兩者都達到最大值)時,
取${\dfrac{t}{d}}={\dfrac{(2^{32})-1}{2^{32}}}$
會發現${(
{\dfrac{t}{d}}+\dfrac{{r\prime}\cdot{n}}{d\cdot{2^k}}})$ < 1 ,
由於${\dfrac{(2^{32}-1)-1}{2^{32}-1}} < {\dfrac{(2^{32})-1}{2^{32}}}$,
因此就算${\dfrac{t}{d}}$為最大值(${\dfrac{(2^{32}-1)-1}{2^{32}-1}}$),
${(
{\dfrac{t}{d}}+\dfrac{{r\prime}\cdot{n}}{d\cdot{2^k}}})$ < 1。
所以可以用$\lfloor{(
\lceil\dfrac{2^k-1}{d}\rceil\cdot{\dfrac{n}{2^k}}})\rfloor$求得$\lfloor{\dfrac{n}{d}}\rfloor$。
### 測驗 4
```c=
bool divisible(uint32_t n)
{
return n * M <= M - 1/*YYY*/;
}
```
因為${M}=\lfloor\dfrac{2^{64}-1}{d}\rfloor + 1=0x5555555555555556$ , 當 M 與其他數相乘時可以分成三種 case:
1. $M\cdot({3m})=(0x{\overbrace{5555....5555}^{共16個}}+1)\cdot({3m})=0+2m$
3. $M\cdot({3m+1})=(0x{\overbrace{5555....5555}^{共16個}}+1)\cdot({3m+1})=0+2m+(M-1)$
4. $M\cdot({3m+2})=(0x{\overbrace{5555....5555}^{共16個}}+1)\cdot({3m})=0+2m+2(M-1)$
當 m >= 0 時,只有$M\cdot({3m})<(M-1)$。故要判定某數 n 是否為 3 的倍數時,只要確定$M\cdot({n})<(M-1)$即可。
### 測驗 5
```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(0x80/*VV1*/)) == 0) { /* is ASCII? */
uint64_t A = *chunk + PACKED_BYTE(128 - 'A' + 0/*VV2*/);
uint64_t Z = *chunk + PACKED_BYTE(128 - 'Z' + (-1)/*VV3*/);
uint64_t mask = ((A ^ Z) & PACKED_BYTE(0x80/*VV4*/)) >> 2;
*chunk ^= mask;
} else
strlower(s, 8);
}
k = n % 8;
if (k)
strlower(s, k);
}
```
從程式碼的後半段開始倒著分析,可以發現關鍵在
```c=
uint64_t mask = ((A ^ Z) & PACKED_BYTE(0x80/*VV4*/)) >> 2;
```
這行,因為 0x80 >> 2 = 1 << 5,而大寫字母 A ^= (1 << 5) 就會轉為小寫。所以只要控制 (A ^ Z) 的值就能決定8個 char 中那一個要轉小寫。
PACKED_BYTE(0x80) = $0x\overbrace{8080...8080}^{共8個80}$,我們需要控制每個80最左邊的1,也就是說(A^Z)的每8個 bits 需要長得像以下形式:
1xxxxxxx、0xxxxxxx。
於是我們必須設定只有當 char 確實是大寫字母時, A 和 Z 的 most significant bit 會不同。
可以使用
* $128+char-'A'$
以及
* $128+char-'Z'-1$
的結果來判斷。只有當 char 為大寫字母時兩個數的 most significant bit 不同。所以我們令:
$A = 128+char-'A'$
$B = 128+char-'Z'-1$>,
透過 (A ^ B)&0x80 來決定對應 mask 的其中8 bits。
以向量的形式表示,就能寫成:
```c=
uint64_t A = *chunk + PACKED_BYTE(128 - 'A' + 0/*VV2*/);
uint64_t Z = *chunk + PACKED_BYTE(128 - 'Z' + (-1)/*VV3*/);
uint64_t mask = ((A ^ Z) & PACKED_BYTE(0x80/*VV4*/)) >> 2;
```
### 測驗 6
```c=
int singleNumber(int *nums, int numsSize)
{
int lower = 0, higher = 0;
for (int i = 0; i < numsSize; i++) {
lower ^= nums[i];
lower &= ~higher/*KKK*/;
higher ^= nums[i];
higher &= ~lower/*JJJ*/;
}
return lower;
}
```
以下參考[csdn yutianzuijin](https://blog.csdn.net/yutianzuijin/article/details/50597413):
如果題目改成除了 Single Number 外每個數皆出現兩次,那只要這麼寫即可:
```c=
int singleNumber2(int *nums, int numsSize)
{
int lower = 0, higher = 0;
for (int i = 0; i < numsSize; i++) {
lower ^= nums[i];
}
return lower;
}
```
每個 bit 的狀態隨 input 的變化可以用以下真值表表示:
|bit state|input|output|
|-|-|-|
|0|0|0|
|0|1|1|
|1|1|0|
|1|0|1|
可以發現因為只有兩個狀態需要記錄(數出現0次或1次),所以只需要1個 bit 記錄1個 bit 的變化。
換成原題的話,有三個狀態(數出現0、1、2次),所以需要2個 bit 記錄狀態(00->01->10->00)。
記錄狀態的 bit 由higher 和 lower 構成,bit 隨input 的變化可以用以下真值表表示:
|higher|lower|input|higher_bit output|lower_bit output|
|-|-|-|-|-|
|0|0|0|0|0|
|0|0|1|0|1|
|0|1|0|0|1|
|0|1|1|1|0|
|1|0|0|1|0|
|1|0|1|0|0|
lower_bit output 即是所求的 Single Number。
可以用以下邏輯表達式表達 lower_bit output:
$$
\begin{aligned}
lower&=lower\cdot\bar{higher}\cdot\bar{input}+\bar{lower}\cdot\bar{higher}\cdot{input}\\
&=\bar{higher} \cdot(lower\cdot\bar{input}+\bar{lower}\cdot{input})\\
&=\bar{higher}\cdot(lower\oplus{input})
\end{aligned}
$$
另外,higher 更新時的所用的 lower 值為新的 lower 值。將新的 lower 值作為輸入,可以畫出以下真值表:
|higher|lower|input|higer_bit output|
|-|-|-|-|
|0|0|0|0|
|0|1|1|0|
|0|1|0|0|
|0|0|1|1|
|1|0|0|1|
|1|0|1|0|
可以用以下邏輯表達式表達 higher_bit output:
$$
\begin{aligned}
higher&=\bar{lower}\cdot{higher}\cdot\bar{input}+\bar{lower}\cdot\bar{higher}\cdot{input}\\
&=\bar{lower} \cdot(higher\cdot\bar{input}+\bar{higher}\cdot{input})\\
&=\bar{lower}\cdot(higher\oplus{input})
\end{aligned}
$$
至此我們得出 higher 和 lower bit 的邏輯表達式,轉換成 C 程式後即得原問題的解。