# 2020q3 Homework2 (quiz2)
contributed by < `CW-B-W` >
[github](https://github.com/CW-B-W/sysprog2020q3-quiz2)
###### tags: `sysprog2020`
## Outline
[TOC]
## 測驗`1`
### is_ascii
* MMM = `0x8080808080808080`
* 若任一個 char 超過 0x7F 則非 ascii
* 以 `c & 0x80` 判斷是否超過 `0x7F`
* 注意 `c` 範圍必須是 `0x00~0xFF`
* 若 `c` 範圍是 `0x00~0xFF` 且 `bit7` 為 1,則其超出 `0x7F`
* 將 8 個 char 合併在一起,同時做 `& 0x80`,則可得知 MMM 為 8 個 `0x80` 合併在一起
```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;
}
```
* 延伸問題1. `為何用 memcpy`
* 為何不用下列程式碼
```CPP
uint64_t* pPayload = (uint64_t*)(str+i);
if (*pPayload & MMM)
...
```
* 若 str 沒有 `word-aligned` , CPU 有可能會發出 `Alignment Trap`
* memcpy 會先將沒有 word aligned 的前幾 bytes 複製,之後再將有 aligned 的 words 複製
* 但這個問題下,複製量只有 `1 word`
* 只是為了保證不會出錯嗎? (*pPayload 若沒有 word aligned,可能會 error)
* 有效率優勢嗎?
### 延伸問題2: 判斷 string 裡是否都是 english alphabet
* 可參考 測驗`5` 裡 `VV2` 與 `VV3` , 用到的判別法
* 詳細推導參考下方 測驗`5`
```CPP
#include <ctype.h>
#include <stddef.h>
#include <stdint.h>
inline int isAlphabetChar(char c)
{
return
(('A' <= c) && (c <= 'Z')) ||
(('a' <= c) && (c <= 'z')) ;
}
/*
* return whether s only contains english alphabets, i.e., 'a'~'z' or 'A'~'Z'
*/
int isAlphabetString(char *s, size_t n)
{
#define PACKED_BYTE(b) (((uint64_t)(b) & (0xff)) * 0x0101010101010101u)
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)) == 0) { /* is ASCII? */
uint64_t A = *chunk + PACKED_BYTE(128 - ('A' + 0));
uint64_t Z = *chunk + PACKED_BYTE(128 - ('Z' + 1));
uint64_t a = *chunk + PACKED_BYTE(128 - ('a' + 0));
uint64_t z = *chunk + PACKED_BYTE(128 - ('z' + 1));
uint64_t isAlpha = (((A ^ Z) | (a ^ z)) & PACKED_BYTE(0x80));
if (isAlpha != 0x8080808080808080) {
return 0;
}
} else {
return 0;
}
}
k = n % 8;
if (k)
while (k--) {
if (!isAlphabetChar(*s))
return 0;
s++;
}
return 1;
#undef PACKED_BYTE
}
```
* 延伸問題3.
* 目前不懂題意
## 測驗`2`
### haxchar2val
* MMM = `0x40`
* AAA = `3`
* BBB = `6`
* `letter` 的用途在於判別 `in` 是不是 `A~F or a~f`
* `a~f` 的 binary 為 `8'b1100_0xxx`
* `A~F` 的 binary 為 `8'b0100_0xxx`
* 兩者共通點為 `8'b0100_0000`
* `0~9` 的 binary 為 `8'b0011_xxxx`
* 故以 `MASK = 8'b0100_0000 = 0x40` 可確認是否為 `A~F` or `a~f` 且不為 `0~9`
* `(in + shift) & 0x0F` 分析
* 如果 `in = '0'~'9'` ,則 `in & 0x0F` 直接是答案
* 如果 `in = 'a'~'f'` ,則 `(in+0x09) & 0x0F` 是答案
* 如果 `in = 'A'~'F'` ,則 `(in+0x09) & 0x0F` 是答案
* 故 shift 為
* 當 `in = '0'~'9'` , `shift = 0x00`
* 此時 `letter = 8'b0000_0000`
* 當 `in = 'a'~'f' or 'A'~'F'` , `shift = 0x09`
* 此時 `letter = 8'b0100_0000`
* 綜合可得 `0x09 = (letter >> 3) | (letter >> 6)`
```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;
}
```
### 延伸問題 - 32bit hex string to decimal
* 需注意 Machine 為 `Little Endian` or `Big Endian`
* 先將 8 個 char 分別用 hexchar2val 的方式轉成 hex value , 在將此 8 個 values 合併在一個 uint32_t 裡面
* e.g. 輸入 `"0DEFACED"` , 先將其轉為 `0x00_0D_0E_0F_0A_0C_0E_0D` , 再合併為 `0x0DEFACED`
* 合併時需注意 `endian` 問題!!
```CPP
#include <iostream>
#include <cstddef>
#include <cstring>
#include <cstdint>
#include <cassert>
using namespace std;
/* return true if your machine is little-endian */
bool isLittleEndian()
{
const uint8_t a8[8] = {
0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
assert((&a8[1] - &a8[0]) > 0); // make sure address of &a8[0] is before &a8[1]
uint64_t* p64 = (uint64_t*)a8;
return (*p64 == 1);
}
uint32_t hex2val(uint8_t in_vec[8])
{
uint64_t in;
memcpy(&in, (void*)in_vec, sizeof(uint64_t));
const uint64_t letter = in & 0x4040404040404040;
const uint64_t shift = (letter >> 3) | (letter >> 6);
const uint64_t tmp = (in + shift) & 0x0F0F0F0F0F0F0F0F;
uint32_t out = (
(((tmp >> 0) & 0x0F) << 28) |
(((tmp >> 8) & 0x0F) << 24) |
(((tmp >> 16) & 0x0F) << 20) |
(((tmp >> 24) & 0x0F) << 16) |
(((tmp >> 32) & 0x0F) << 12) |
(((tmp >> 40) & 0x0F) << 8) |
(((tmp >> 48) & 0x0F) << 4) |
(((tmp >> 56) & 0x0F) << 0)
);
return out;
}
int main()
{
cout << isLittleEndian() << endl;
uint8_t in_vec[8] = {
'0', 'D', 'E', 'F', 'A', 'C', 'E', 'D'
};
uint32_t dec = hex2val(in_vec);
cout << dec << endl;
return 0;
}
```
## 測驗`3`
### quickmod
* XXX = `0xFFFFFFFFFFFFFFFF`
* 須滿足 `floor(M * n / 2^64) = floor(n / d)` , 故 `M = 2^64 / d` 。 因 `2^64` 超出 `uint64_t` , 故先考慮取 `M = (2^64-1) / d` , 但當 `n 為 d 的倍數` 時算出的 quotient 會少 1 , 故需將 `M` 再加上一 。 得 `M = (2^64-1) / d + 1`
```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;
}
```
## 測驗`4`
### divisible
* YYY = `M - 1`
* 我還沒想透呢
```CPP
bool divisible(uint32_t n)
{
return n * M <= YYY;
}
```
## 測驗`5`
### strlower_vector
* `A = 8'b0100_0001` , `Z = 8'b0101_1010`
* `a = 8'b0110_0001` , `z = 8'b0111_1010`
* VV1 = `0x80`
* 如果 `bit7` 不為 1 , 則判斷其非 `ascii`
* VV2 = `0` & VV3 = `-1`
* `A` 用來判斷是否 char 有 `>= 'A'`
* e.g. `'A' + 128 - 'A' = 128 + 0 = 8'b1000_0000` , 因為 `bit7` 為 `1` ,故其`有`超過 `'A'`
* e.g. `'@' + 128 - 'A' = 128 - 1 = 8'b0111_1111` , 因為 `bit7` 為 `0` ,故其`沒有`超過 `'A'`
* PS: `'@'` 為 `'A'` 在 ascii 上前一個字元
* `Z` 同理,但是要判斷有沒有 ` > 'Z'` , 也就是有沒有 ` >= 'Z' + 1`
* 故 `VV2` 原型為 `c + 128 - ('A'+0)`
* 而 `VV3` 原型為 `c + 128 - ('Z'+1)`
* `A ^ Z` 用來判斷 是否 ` >= 'A'` 與 ` > 'Z'` 只有一個成立,若只有一個成立,則 `A ^ Z = true`,即 `c為需要處理的 byte`;反之若同時成立或同時不成立,i.e. `c > 'Z'` or `c < 'A'` ,則 `A ^ Z = false`,即 `c 為不需處理的 byte`
* 延伸用法可見 測驗`1`--延伸問題`2`
* VV4 = `0x80`
* 因為判斷是否 `'A' <= c <= 'Z'` 只需要 `bit7` , 故將 `bit6~bit0` 全設為 0
```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);
}
```
* 延伸問題2. 將 `char str[]` 改為 `char* str`
* C 語言規格書 [ISO/IEC 9899:1999](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf) p.130
* 感謝 `Rsysz` 同學,因為我參考了他的回答,找到了頁數
> On the other hand, the declaration
> char* p = "abc";
> defines p with type ‘‘pointer to char’’ and initializes it to point to an object with type "array of char" with length 4 whose elements are initialized with a character string literal. If an attempt is made to use p to
modify the contents of the array, the behavior is undefined.
* 改成 `char* str` 會 `segmentation fault`
* 通常此時 str 指向 `read-only` 的 `text section` (string literal 一般存在此) , 因為 `strlower_vector` 會更動到 `str` 裡面的值,故會造成嘗試寫入被保護的 `text section` 而導致 `segmentation fault`
* Reference: [Stackoverflow: Why do I get a segmentation fault when writing to a “char *s” initialized with a string literal, but not “char s[]”?](https://stackoverflow.com/questions/164194/why-do-i-get-a-segmentation-fault-when-writing-to-a-char-s-initialized-with-a)
* Reference: [Wikipedia: Data segment](https://en.wikipedia.org/wiki/Data_segment)
* 用 `char str[]` 則會在 `stack` 新配置一塊空間,再將 `string literal` 內容 copy 到此空間,故沒有問題
## 測驗`6`
### Single Number II
* KKK = `~higher`
* 我還沒想透呢
* JJJ = `~lower`
* 我還沒想透呢
* 雖然還沒想透,但這篇的實作法直觀上比較容易理解 [【leetcode】在一堆每个数字都出现三次的数组中,找到那个只出现一次的数(Single Number II)](https://blog.csdn.net/exceptional_derek/article/details/17636909)
```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;
}
```