# 2017q3 Homework1 (clz)
contributed by < `lukechin` >
作業要求: https://hackmd.io/s/Hyl9-PrjW
## clz的應用案例
* 測試兩數是否相等
設兩數為 $x$ 與 $y$,
在 32-bit 無符號數值範圍可由 $clz(x−y)$` >> `$5$,得知
因為當 $x$ 與 $y$ 相等時,$clz(x-y)$ 為 $32$,在右邏輯位移 $5$ 後,得 $1$;
$x$ 與 $y$ 不相等 $clz(x-y)$ 則不為 $32$,位移後,得 $0$。
```
is_equal(unsigned int x, unsigned int y){
return clz(x-y)>>5;
}
```
* [Binary GCD](https://en.wikipedia.org/wiki/Binary_GCD_algorithm)
binary gcd 的精神就是當兩數為偶數時,必有一 $2$ 因子。
$$gcd(x, y) = 2·gcd(\frac{x}{2}, \frac{y}{2})$$
且一數為奇數另一數為偶數,則無 $2$ 因子。
$$gcd(x, y) = gcd(x, \frac{y}{2})$$
於是我們可以改良為:
$$ even\_factor = min(ctz(x), ctz(y))$$
$$gcd(x, y) = even\_factor·gcd((x\gg even\_factor), (y\gg even\_factor))$$
其中符號 $\gg$ 是 right shift。
剩下的過程就直接採用 Euclidean algorithm 的作法即可。
## 簡介clz
Count Leading Zeros (clz) 或名 Number of Leading Zeros (nlz) 為計算 2 進位數從 MSB 往右數遇到的第一個 $1$ 之前所有 $0$ 的數量, e.g. clz(`0001010100011011`) = $3$。
## 實做方式
### 利用 binary search
binary search 是一種分而治之的方式,是將問題分成兩個部份,
然後將最接近解的部份令為下一個待解的子問題,
子問題又能分成兩個部份,其中一個部份又能變為子問題,直到找到解。
#### iterative
```clike=
int clz(uint32_t x) { // l := left, r := right
uint32_t n = 32, c = 16, l, r = x;
do {
l = r >> c;
if (l) { n -= c; r = l; }
c >>= 1;
} while (c);
return (n - r);
}
```
假設一個數字 ```00001010100011011100001010100101```
```x = 00001010100011011100001010100101```, $c = 16$, $n = 32$
```...._______________^________________```
```x = 00000000000000000000101010001101```, $c = 8$, $n = 16$
```...._______________^_______^________```
```x = 00000000000000000000000000001010```, $c = 4$, $n = 8$
```...._______________^_______^___^____```
```x = 00000000000000000000000000001010```, $c = 2$, $n = 8$
```...._______________^_______^___^_^__```
```x = 00000000000000000000000000000010```, $c = 1$, $n = 6$
```...._______________^_______^___^_^^_```
```x = 00000000000000000000000000000001```, $c = 0$, $n = 5$
$r = 1$, 最後```return (n - r);``` i.e. clz = 4.
### bit mask
作業中稱這個方法為 binary search 怪怪的,因為其他兩份 code 也是 binary search,決定將檔案都改成 bit mask
```clike=
int clz(uint32_t x) {
if (x == 0) return 32;
int n = 0;
if (x <= 0x0000FFFF) { n += 16; x <<= 16; }
if (x <= 0x00FFFFFF) { n += 8; x <<= 8; }
if (x <= 0x0FFFFFFF) { n += 4; x <<= 4; }
if (x <= 0x3FFFFFFF) { n += 2; x <<= 2; }
if (x <= 0x7FFFFFFF) { n += 1; x <<= 1; }
return n;
}
```
利用 bit mask 的概念去判定第一個 $1$ 出現在哪邊。
#### byte shift
```clike
int clz(uint32_t x) {
if (x == 0) return 32;
int n = 1;
if ((x >> 16) == 0) { n += 16; x <<= 16; }
if ((x >> 24) == 0) { n += 8; x <<= 8; }
if ((x >> 28) == 0) { n += 4; x <<= 4; }
if ((x >> 30) == 0) { n += 2; x <<= 2; }
n = n - (x >> 31);
return n;
}
```
只有 if 判斷跟 bit mask 寫法不同,但邏輯上是一樣的。
### Hash Table
#### De Bruijn sequence
TODO: 解釋 De Bruijn sequence
```Clike=
const int tab32[32] = {
0 , 1 , 28, 2 , 29, 14, 24, 3,
30, 22, 20, 15, 25, 17, 4 , 8,
31, 27, 13, 23, 21, 19, 16, 7,
26, 12, 18, 6 , 11, 5 , 10, 9
};
int clz(uint32_t value) {
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
return tab32[((uint32_t)((value - (value >> 1))*0x077CB531U)) >> 27];
}
```
先將 value 變為除了第一個 $1$ ,其餘的 bit 都設為 $0$
接著用```0x077CB531U```這個 De Bruijn sequence 乘上 value,在將 table 映射到的值回傳。
#### Harley's algorithm
```clike=
uint8_t clz(uint32_t x)
{
static uint8_t const Table[] = {
0xFF, 0, 0xFF, 15, 0xFF, 1, 28, 0xFF,
16, 0xFF, 0xFF, 0xFF, 2, 21, 29, 0xFF,
0xFF, 0xFF, 19, 17, 10, 0xFF, 12, 0xFF,
0xFF, 3, 0xFF, 6, 0xFF, 22, 30, 0xFF,
14, 0xFF, 27, 0xFF, 0xFF, 0xFF, 20, 0xFF,
18, 9, 11, 0xFF, 5, 0xFF, 0xFF, 13,
26, 0xFF, 0xFF, 8, 0xFF, 4, 0xFF, 25,
0xFF, 7, 24, 0xFF, 23, 0xFF, 31, 0xFF,
};
/* Propagate leftmost 1-bit to the right */
x = x | (x >> 1);
x = x | (x >> 2);
x = x | (x >> 4);
x = x | (x >> 8);
x = x | (x >> 16);
/* x = x * 0x6EB14F9 */
x = (x << 3) - x; /* Multiply by 7. */
x = (x << 8) - x; /* Multiply by 255. */
x = (x << 8) - x; /* Again. */
x = (x << 8) - x; /* Again. */
return 31 - Table[x >> 26];
}
```
先將輸入 `x`, MSB 至第一位 `1` 以後的位全變為 `1`, e.g. `00100100 -> 00111111`
再將 `x` 乘上 `0x6EB14F9`,接著位移至剩下 $5$ 位,查表並用 $31$ 減去。
`0x6EB14F9` 能產出的值為 $0$ ~ $63$ 之間 $32$ 種互不相同的數字,並且 `0xFF` 並沒有特別的意義。
## 程式執行
* 輸入 `make run`
* 畫圖 `make plot`,數值範圍從 67100000 到 67116384
![](https://i.imgur.com/pdC3iey.png)
從圖片中可以發現一開始效能比較是
**bit mask >= byte > iteration > harley > recursive**
可以發現 recursive 的版本所需時間跳動範圍大的多,試著縮小數值範圍來觀察
![](https://i.imgur.com/WC9XiLe.png)
根據劉亮谷學長的開發紀錄,會造成上下跳動的原因應該是因為它特別設計的演算法的關係,能讓特定數值速度加速