# 2018q3 Homework (quiz1)
###### tags: `bauuuu1021`,`BroLeaf`
contributed by <`bauuuu1021`,`BroLeaf`>
[2018q1 第 1 週測驗題](https://hackmd.io/s/rJh9U4Guf)
---
## 測驗 `1`
考慮以下程式碼:
```C
#include <stdlib.h>
int isMultN(unsigned int n) {
int odd_c = 0, even_c = 0; /* variables to count odd and even SET bits */
if (n == 0) // return true if difference is 0.
return 1;
if (n == 1) // return false if the difference is not 0.
return 0;
while (n) {
if (n & 1) // odd bit is SET, increment odd_C
odd_c++;
n >>= 1;
if (n & 1) // even bit is SET, increment even_c
even_c++;
n = n >> 1;
}
/* Recursive call till you get 0/1 */
return(isMultN(abs(odd_c - even_c)));
}
```
其作用為檢查輸入整數是否為 N 的倍數,那麼 N 為多少?
:::success
延伸問題:
* 為什麼上述程式碼可運作呢?
* 將 N 改為其他質數再改寫為對應的程式碼,一樣使用遞迴
:::
* 紀錄 input n 的**奇、偶數位為 1** 的有幾個 bit 在odd_c 、 even_c 裡,然後取兩者差的絕對值再遞迴呼叫
* 而兩者的差值要同時也是 N 的倍數、或是 0 才會回傳 true.
* 帶幾個數字 0011、0111、1111 就會馬上知道 N 是 3
* 這是因為 3 可以寫成 0011 而在 3 的倍數上一直加 3 ,odd_c 、 even_c 的差值會有兩種結果出現
* 差值相等 (0011+0011 = 0110)
* 相差為 3n (10101+00011 = 11000)
這種說法雖然直觀,但是不嚴謹,所以我們證明他的推廣
* 詳細的證明:
**證明**
試證:三的倍數,皆為奇偶數位 1 個數差為 0 或 3n 的數
奇數位的 `1` 代表 2^2n^ 也就是 :1, 4, 16, 64…這些數共同的特色是 3$p_i$+1
偶數位的 `1` 代表 2^2n+1^ 也就是 :2, 8, 32, 128…這些數共同的特色是 3$q_i$-1
因此可得到,
奇偶數位 `1` 的個數相等時:代表 3(P+Q) 為三的倍數
奇偶數位 `1` 的個數相差3的倍數:代表 3P+3 or 3Q-3皆為三的倍數
p.s. P = Σpi , Q = Σqi
得證假設成立。
:::warning
以電路設計的觀點,檢視上面遞迴程式的實作,倍頻和除頻的電路是否有相似之處?
:notes: jserv
:::
```clike=
module div2_v2 (
input clk,
input rst_n,
output reg o_clk
);
reg cnt;
always@(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 0;
else if (cnt == 1) // 0 ~ 1
cnt <= 0;
else
cnt <= cnt + 1;
end
always@(posedge clk or negedge rst_n) begin
if (!rst_n)
o_clk <= 0;
else if (cnt < 1) // 0
o_clk = 0;
else // 1
o_clk = 1;
end
endmodule
```
* 用 verilog 實作的除 2 的除頻器,第 7 ~ 14 行是一個簡單的計數器, 15 ~ 22 行就是主要的除頻,這段也是主要和題目相似的地方,根據計數器的值,來決定 o_clk 的 output 。
### 將 N 改為其他質數再改寫為對應的程式碼
* 為了能將 N 推廣到更多的數,所以我想了一個能夠一般化的方法
#### 改寫演算法
* 我在計算的過程中發現,只要是某個奇數 N ,在求 N mod 2^m^, 2^m+1^, 2^m+2^ ...所得到的值會不斷變化,且最後一定會形成一個循環。很明顯,當 N 愈大,一次循環的數就愈多,而有可能的值也會變多。
* 只有當 N = 2^m^ - 1 時,可以確定每 m 個 bit 一個循環
|N mod 2^m^|1|2|4|8|16|32|64|128|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|3|1|2|1|2|1|2|1|2|
|5|1|2|4|3|1|2|4|3|
|7|1|2|4|1|2|4|1|2|
|15|1|2|4|8|1|2|4|8|
* 而當要計算某數 A 是不是 N 的倍數時,就把一次讀一個循環的 bit ,再把 mod 過後的值當成加權,個別乘上
* 例如:N = 5 ,就一次讀 4 個 bit (看上表,每 4 個 bit 循環)第一個 bit 乘上 1 、第二個 bit 乘上 2 、第三個 bit 乘上 4 、第四個 bit 乘上 3 ,加總起來後繼續讀下去,全部加總後再把值呼叫遞迴繼續執行,若 A 為 5 的倍數,則最後一定得出 5 或 0。
* 實際判斷一次:判斷 77(1001101)是不是 7 的倍數帶入公式,從最小的 bit 開始, $1\times1+0\times2+1\times4+1\times1+0\times2+0\times4+1\times1 = 7$,所以得到 77 是 7 的倍數。
* 用更直白的話解說就是,把 A 寫成二進位,把每個 bit 所代表的 2 的某次方去 mod N ,把結果加總起來,再重來,直到結果變成 N 或是 < N
* N = 5
```C
int isMult5(unsigned int n)
{
int ret = 0;
if (n == 0 || n == 5) // return true if the input is 0 or 5
return 1;
if (n < 5) // return false if the input is not 0.
return 0;
while (n)
{
if (n & 1) // first bit, weighted is 1.
ret++;
n >>= 1;
if (n & 1) // second bit, weighted is 2.
ret+=2;
n >>= 1;
if (n & 1) // third bit, weighted is 4.
ret+=4;
n >>= 1;
if (n & 1) // fourth bit, weighted is 3.
ret+=3;
n >>= 1;
}
return(isMult5(ret - 5));
}
```
* 最後之所以要 - 5 是因為避免傳入 6 or 7 導致遞迴不能終止
* N = 7
```clike
int isMult7(unsigned int n)
{
int ret = 0;
if (n == 0 || n == 7) // return true if the input is 0 or 7
return 1;
if (n < 7) // return false if the input is not 0.
return 0;
while (n)
{
if (n & 1) // first bit, weighted is 1.
ret++;
n >>= 1;
if (n & 1) // sedcond bit, weighted is 2.
ret+=2;
n >>= 1;
if (n & 1)
ret+=4; // third bit, weighted is 4.
n >>= 1;
}
return(isMult7(ret));
}
```
* 當 N = 2^m^ - 1 時,還有另一個性質,當被帶入的數字作上面提到的加權計算時,其實結果就跟直接對 N 作 and 是一樣的,在這個前提下,直接修改 N = 7 裏面 while 的程式碼,再加上 N = 31 的實作
* N = 7
```clike
int isMult7(unsigned int n)
{
int ret = 0;
if (n == 0 || n == 7) // return true if the input is 0 or 7
return 1;
if (n < 7) // return false if the input is not 0.
return 0;
while (n)
{
ret += n & 7;
n >>= 3;
}
return(isMult7(ret));
}
```
* N = 31
```C
int isMult31(unsigned int n)
{
int ret = 0;
if (n == 0 || n == 0X1F) return 1;
if (n < 31) return 0;
while (n)
{
ret += n & 0X1F;
n >>= 5;
}
return(isMult31(ret));
}
```
## 測驗 `2`
給定 B 為 2 的某次方 (power of 2),那麼是否 `A % B` 等價於 `(A & (B - 1))`?
:::success
延伸問題:
* 為什麼?
* 舉出類似的案例並解釋
:::
* 對任意正整數 x 除以任意正整數 m ,得商為 k 餘數為 r。可寫為 $x=k*m+r$,r 亦可寫為 x%m
* 可將題目的 A 拆成兩項相加:$hi_A$,$lo_A$
* $hi_A$ (k*B) 為 A 中 >= B 部份
* $lo_A$ ( r ) 為 A 中 < B 部份
* 則 A % B = A/B 的餘數 = $lo_A$,即取比 B 小的所有位數
* 假設 B 為 2 的 n 次方,為 1000...0 的形式(n 個 0);(B-1) 為 111...1 (n 個 1) 形式,`(A & (B - 1))` 即可取得比 B 小的部份
|A|$x_1$|$x_2$|$x_3$|$x_4$|$x_5$|...|...|...|$x_m$|
|---|---|---|---|---|---|---|---|---|---|
| B | | | 1 | 0 | 0 | ... |... | ... | 0 |
| B-1 | | | | 1 | 1 | ... | ... | ... | 1 |
| A%B | | | | $x_4$ | $x_5$ | ... |... | ... |$x_m$ |
---
## 測驗 `3`
在 C 程式中,表達式 `1 << 2 + 3 << 4` 求值後為何?
:::success
延伸問題:
* 在 C 語言規格書中,找出對應運算子優先權的描述並嘗試解讀
:::
* 在 [C 語言規格書](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf) 的 6-5 : precedence of operators 中有以下註解:
`The syntax specifies the precedence of operators in the evaluation of an expression, which is the same as the order of the major subclauses of this subclause, highest precedence first.`
意思也就是說運算子的優先權和它在規格書中介紹的順序先後相同,先介紹到的運算子有較高優先權。
* 題目中的 `+` 在 6.5.6 Additive operators 中被定義, `<<` 則到了 6.5.7 Bitwise shift operators 才被定義。因此必須先執行 `+` 再執行 `<<`。
* 答案相當於 `1<<(2+3)<<4 = 512`
:::warning
不用把完整表格列出,但需要將 C99/C11 對應的原文列出!
:notes: jserv
:::
* 附上 [cppreference.com](http://en.cppreference.com/w/c/language/operator_precedence) 所整理的表格
| Precedence | Operator | Description | Associativity |
| --------- | ------ | --------- | ------ |
|1 | ++ -\-<br>()<br>[ ]<br>.<br>-><br>(type){list} |Suffix/postfix increment and decrement<br>Function call<br>Array subscripting<br>Structure and union member access<br>Structure and union member access through pointer<br>Compound literal|Left-to-right|
|2|++ -\-<br>+ -<br>! ~<br>(type)<br>*<br>&<br>sizeof<br>_Alignof |Prefix increment and decrement<br>Unary plus and minus<br>Logical NOT and bitwise NOT<br>Type cast<br>Indirection (dereference)<br>Address-of<br>Size-of<br>Alignment requirement |Right-to-left
|3 |* / % |Multiplication, division, and remainder |Left-to-right|
|4 |+ - |Addition and subtraction|
|5 |<< >> |Bitwise left shift and right shift|
|6 |<<=<br> >>= |For relational operators < and ≤ respectively<br>For relational operators > and ≥ respectively|
|7 |== != |For relational = and ≠ respectively|
|8 |& |Bitwise AND|
|9 |^ |Bitwise XOR (exclusive or)|
|10 | \| | Bitwise OR (inclusive or)
|11 |&& |Logical AND
|12 | \|\| |Logical OR
|13| ?: |Ternary conditional |Right-to-Left
|14 |=<br>+= -=<br>*= /= %=<br><<= >>=<br>&= ^= \|=|Simple assignment<br>Assignment by sum and difference<br> Assignment by product, quotient, and remainder<br> Assignment by bitwise left shift and right shift <br>Assignment by bitwise AND, XOR, and OR|
|15 |, |Comma |Left-to-right
:::warning
摘錄並且解讀!思考為何 C 語言規格要這樣制定優先權順序 (提示: Python 也有類似的規範)
:notes: jserv
:::
---
## 測驗 `4`
考慮某些硬體平台執行乘法的時間較長,我們會以 `(x << 5) - (x)` 取代 `*` 31,那麼 `*` (-6) 該如何用 shift 搭配 add/sub 實作呢?
:::success
延伸問題:
* 乘以某個整數的運算,如果拆解為 shift 和 add/sub 的組合,是否存在最小值的唯一解?
:::
* 參考 CS:APP 3/e `2.3.6 乘以常數` (第 71 頁)
:::warning
參考資料時,應該附上章節的名稱
:notes: jserv
:::
* 以 $x * 14$ 為例;因為 $14=2^3+2^2+2^1$,所以可以將 $x * 14$ 改寫成 $x*2^3+x*2^2+x*2^1=(x<<3)+(x<<2)+(x<<1)$。又因 $14=2^4-2^1$,所以也可以寫成 $(x<<4)-(x<<1)$。
* 題目欲求的 $*(-6)$ 可以改寫成 $-2^2-2^1$ 或 $-2^3+2^1$,但取後者可以少做一次減法。得 $x *(-6)=(x<<1)-(x<<3)$ ,所以答案為 `(c) 2 個 shift 搭配 1 個 add/sub`
* CS:APP 中有歸納出: $x*K$ 中的 K 如果可以表示成 [$1^*(111)0^*$] 的 [Regular Expression](https://en.wikipedia.org/wiki/Regular_expression) ,有最小唯一解。其中最高位的 1 在第 n 位,最低位的 1 在第 m 位。
* $K = 2^n+2^{n-1}+...+2^m = 2^{n+1}-2^m$
* 因此 $x*K$ 可寫為:
* $x*K = x*(2^n+2^{n-1}+...+2^m) = x*2^n +x*2^{n-1}+...+x*2^m$ <br> $= (x<<n)+(x<<(n-1))+...+(x<<m)$
* $x*K = x*(2^{n+1}-2^m) = x*2^{n+1}-x*2^m = (x<<n+1)-(x<<m)$
* 由於上述第 1 種表示法有多少 1 就要寫成多少項相加,第 2 種不論 1 的數量有多少都只有兩項。顯然 K 可表示成 [$1^*(111)0^*$] 形式時,因為 1 的數量必$>=3$,相較而言第 2 種方式可以用較少運算次數達成目標。
* 因此當 K 可表示成 [$1^*(111)0^*$] 形式時,有最小唯一解
:::warning
TODO: 找出編譯器最佳化過程中,透過上述技巧改寫整數乘法的案例
:notes: jserv
:::
* 寫了以下程式
```C=
#include <stdio.h>
int main () {
int x=11, y=0;
y=x*31;
}
```
* 並透過 `gcc -S` 得到 assembly code,以下擷取部份
```=4
...
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $11, -8(%rbp)
movl $0, -4(%rbp)
movl -8(%rbp), %edx
movl %edx, %eax
sall $5, %eax
subl %edx, %eax
movl %eax, -4(%rbp)
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
...
```
* [指令](http://www.cs.princeton.edu/courses/archive/spr17/cos217/lectures/13_Assembly1.pdf) 形式:name{b,w,l,q} src, dest
* byte(b) ⇒ operands are one-byte entities
* word(w) ⇒ operands are two-byte entities
* long(l) ⇒ operands are four-byte entities
* quad(q) ⇒ operands are eight-byte entitles
* 第 13.14 行將 x,y 分別放入初值
* 第 15.16 行將 x 值額外存入暫存器 %edx, %eax
* 第 17 行將 %eax 往左移 5 位
* 第 18 行將 %eax 減去 %edx 再存入 %eax
* 第 19 行將 %eax 中值存回 y
---
## 測驗 `5`
考慮以下整數乘法的實作程式碼:
```C
int mul(int n, int m)
{
int ret = 0;
for (int c = 0; m; c++, m /= 2)
if (!(m % 2 - 1)) OP
return ret;
}
```
上述 `OP` 對應到下方哪個程式敘述呢?
:::success
延伸問題:
* 解釋為何乘法可運作?
:::
* 先把程式碼做一些分析,看到函式 OP 前面的條件式
```
!(m % 2 - 1)
```
* 把他分解來看
* `(m % 2)` 如果 m 是奇數,就是 true
* `(m % 2 - 1)` 則反過來,如果 m 是偶數,才是 true
* `!(m % 2 - 1)` 就簡單了,如果 m 是奇數,就是 true
* 再來看 for loop 每次的變化 `m /= 2` 看到這裡應該就明白, for loop 每次做的就是看 m 的二進位表示,最右邊一位是不是 1 ,如果是的話就執行 OP
* 因為 m /= 2 就相當於右移 1 ,所以在每次執行 OP 時,會把 ret 加上相對應的 n * 2^c^ ,所以可以算出 ret += n << c
* 為何 m 為負,此演算法就失效 ?
根據[ Modulo operator with negative values ](https://stackoverflow.com/questions/7594508/modulo-operator-with-negative-values) 得知,若要算 `x%y` ,電腦其實是做 `x-(x/y)*y` ,因此若 m 是負數,`m % 2` 就會變成是做 `m-(m/2)*2` ,得到的結果不是 0 就是 -1 ,會導致 **if 永遠不成立,因此最後的 ret 永遠都是0**。
* 而會造成上述這個原因是因為我們預測餘數不是 0 就是1,但是若 m 是負數,餘數就會是 0 或是-1,因而使演算法失效。
因此,根據上面的觀察,只要把 code 改為以下,就能支援 signed number。
```clike=
int mul(int n, int m)
{
int ret = 0;
for (int c = 0; m; c++, m /= 2)
{
if (!(m % 2 - 1)) ret += n << c;
else if ((m % 2)!=0) ret -= n << c;
}
return ret;
}
```
### 延伸問題 : 解釋為何乘法可運作?
如同我上面所述的,每次只看 m 的一個 bit 去對 n 作位移,再把所有結果加總起來,如果用數學式子可以表達成:
令 $m = (c_0*2^0 + c_1*2^1 + c_2*2^2+......+c_n*2^n)$
$\Rightarrow n*m = n*(c_0*2^0 + c_1*2^1 + c_2*2^2+......+c_n*2^n)$
展開後第 n 項就是 for loop 跑第幾次所加上去的值
:::warning
繼續以邏輯電路的設計,思考乘法器的實作
:notes: jserv
:::
## 參考資料
* [2018q1 Homework2 (作業區)](https://hackmd.io/s/BykAwRuKz)
* [bauuuu1021共筆](https://hackmd.io/s/Bkmr6V6tM#%E7%AC%AC-1-%E5%91%A8%E7%AC%AC-4-%E9%A1%8C)
* [BroLeaf共筆](https://hackmd.io/s/B1QbUVz9G)
* [e94046165共筆](https://hackmd.io/s5qyemoWSUKynwvIUFOcog#%E6%B8%AC%E8%A9%A6amp%E9%87%8D%E8%AE%80%E7%A8%8B%E5%BC%8F%E7%A2%BC)
* [rex662624共筆](https://hackmd.io/s/r189YEBFf)
* [solution](https://legacy.gitbook.com/book/dreamanddead/csapp-3e-solutions/details)