# 2020q3 Homework2 (quiz2)
contributed by < ddj5523fa >
---
### 想法 & 思考
測驗一
題目:
```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;
}
```
ANS: MMM = (d) 0x8080808080808080 。
回答:
1.
(1)該題前段有個比較原始版的程式碼提到0x80,首先先知道0x80是128的16進制
轉2進制是1000 0000 ;而ACII 是0~127,所以當str[i]跟0x80會等於true ,代表str[i]大於等於128,超過ACII的範圍了,以此來判斷is_ascii。
(2)上面程式碼則是在 64 位元的架構下,我們嘗試一次比對 64 位元的資料 (即 word size)。上面程式中,str中一個是1byte(8位元),而一次檢查64bit(8byte),所以每一輪過後 "+8"。
(3)memcpy() 的實做是跟data alaigment有關,會先使 memory address目前位址到 word aligned 的位址,然後以 word 為單位做複製。
[此段是參考某臉書的貼文](https://www.facebook.com/pcman.im/posts/1623916640981340/)
還有參考:作業中延伸學習的提示與詳閱。
(4)承上(1)、(3)得知每次進行式一個word(64bbit是8byte),而每byte中要判定其是否是 ACII 是與 0x80 做 AND,所以8byte便是 ==0x8080808080808080==
2.判斷是否是英文字母,也許可參考測驗2解析器 (parser)。//=="未完"==
-------------------------------------------------------
測驗二
開發解析器 (parser) 時,常會將十六進位表示的字串 (hexadecimal string) 轉為數值,例如 '0xF' (大寫 F 字元) 和 '0xf' (小寫 f 字元) 都該轉換為 15。考慮以下不需要分支 (branchless) 的實作:
```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;
}
```
1.
(1)0x30的2進制為0011 0000 =>0x31到0x39都是數字
0x40的2進制為0100 0000
0x60的2進制為0110 0000
=>0x41以後包含了大小寫英文字母。
所以程式碼==const uint8_t letter = in & MASK;==
是先區別開數字與英文字母。
而==MASK=0x40== <ANS>
(2)接下來透過觀察可知:(拿0(0x30)a(0x41)、A(0x61)來比較與觀察)
|EX:|0|a|A|
|----|----|----|----|
|HEX:|0x30|0x41|0x61|
|Binary:|0011 0000|0100 0001|0110 0001|
|output(val):|0|10|10|
|output(Binary)|0000 0000|0000 1010|0000 1010|
而依據程式碼可得知:如果in是英文字母,則letter=0x40=0100 0000
所以讓letter可以變成A的output這樣的話便是右移3格與右移6格的letter做 OR:會得0000 1001 (相當於+9)配合後面return的程式碼。
==const uint8_t shift = (letter >> AAA) | (letter >> BBB);==
==可得知AAA、BBB的答案為3與6。== <ANS>
----------------------------------------------------
測驗三:
```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;
}
```
原理:
1.首先依題目,其打算先取得餘數與商數(quotient) 然後可簡化成 * 與+ 還有位移的運算。
2.先從上面程式碼quickmod func.開始看起,得知商等於M*n>>64;而依據題目我們可知道: $$quotient = \frac{n}{D} = n \times \frac{2^N}{D} \times \frac{1}{2^{N}}$$
==p.s.== 題目上此處提示寫成了$$ n \times \frac{\frac{2^N}{d}}{2^N} $$讓人會誤以為是變成 $$ {2^{N}} \times {2^{N}} $$再除 d ,思考了很久。
3. 上述程式碼:quotient = ((__uint128_t) M * n) >> 64;
可以知道 >>64 可看作除以2的64次方,根據承2. 的公式:
得到 $$ \frac{n}{D} = n \times M \times \frac{1}{2^{64}}$$
代入 M 的定義: $$ \frac{n}{D} = n \times \frac{1}{2^{64}} \times (\frac{(XXX)}{D}+1)----(1)$$
應該要符合,$$quotient = \frac{n}{D} = n \times \frac{2^N}{D} \times \frac{1}{2^{N}}----(2)$$ 所以可得知 XXX 中必定包含 << 64 。
上述的兩等式(1)(2)合併一下 可得出:$$ (\frac{(XXX)}{D}+1)=\frac{2^N}{D} $$ ,(此時N=64) ; $$ XXX={2^{64}}-1 $$
==答案:XXX = 0xFFFFFFFFFFFFFFFF==
----------------------------------------------------
測驗四:
假設D為d時,$\frac{n}{d}$的餘數為0,1,2,...,d-1 可分別討論:
1.餘數=0時候:(n set k*d+0)
=> $n \times M = k*d \times (\frac{2^{64}-1}{d}+1) = k*2^{64}-k +kd + 0M$
2.餘數=1,n set kd+1:
=> $n \times M = (kd+1) \times (\frac{2^{64}-1}{d}+1) = k*2^{64}-k +kd + M$
3.餘數=2,n set kd+2:
=> $n \times M = (kd+2) \times (\frac{2^{64}-1}{d}+1) = k*2^{64}-k +kd + 2M$
4.餘數=3,n set kd+3:
=> $n \times M = (kd+3) \times (\frac{2^{64}-1}{d}+1) = k*2^{64}-k +kd + 3M$
.
.
.
.
5.當餘數來到d-1時候 ,n set kd+(d-1)
=> $n \times M = (kd+(d-1)) \times (\frac{2^{64}-1}{d}+1) = k*2^{64}-k +kd + (d-1)M$
=>根據上面觀察 只有1.能被整除 而1 沒有像其他假設一樣有多出?個M。
==補充==:上述有提到的,每個都會算出 $k*2^{64}-k$,在64位元下$2^{64}$的會變成0(有數值1的部分溢位被除去了),所以$k*2^{64}-k +kd + 0M$會變成 $0-k+kd+0M$會小於M
所以,n*M < M時候代表可整除, 而程式碼為<= 所以選擇題選項應選擇M-1!
----------------------------------------------------
測驗五:程式碼有用到在測驗二上
----------------------------------------------------
測驗六:
以下程式碼為 LeetCode 137. Single Number II 的題解:
```cpp
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;
}
```
1.
(1)第一個想法是,A = A XOR B 兩次會變回原本的,所以lower在XOR後,下一行lower &=KKK 不應該改變結果,否則可能影響XOR的用處。然後實際用例子去run一次試試看!,KKK若不是因為選擇題,(我可能會持續想很久也不見得有答案。)
延伸學習2+3.
[參考網址](https://www.cnblogs.com/grandyang/p/4263927.html)
2.Leetcode 我是依據上面參考網址教學的其中一種方式寫的。
3.延伸衝婦到4次5次甚至有規律個式子,我以上述的一個方法覺得比較好整合來作為我的實作:
```cpp=
int singleNumber(int nums[], int numsSize, int times)
{
int res = 0;
for (int i = 0; i < 32; ++i)
{
int sum = 0;
for (int j = 0; j < numsSize; ++j)
{
sum += (nums[j] >> i) & 1;
}
res |= (sum % times) << i;
}
return res;
}
```
此方法思路為:以每一個bit分別做Sum,再去mod 次數(重複3、4、5...)
最後每一bit留下的值組合出的數字便是沒有單獨的該解答!
所以程式碼中第9行便是一直左移將bit拿到 &1是為了確保只有比較最低位元。
res |= (sum % fre) << i; 便是將位元左移損失的倍率還原回來。
for迴圈用32次是因為int: 4個位元組 所以是32 bit。
不過此方法在leetcode上不會過關,因為當字串遇到負數會runtime error。
所以我在此程式碼基礎上```res |= (sum % times) << i;```多加了(unsigned int)
=>``` res |= (unsigned int)(sum % 3) << i; ```