# 2022q1 Homework3 (quiz3)
contributed by < `Tcc0403` >
[作業需求](https://hackmd.io/@sysprog/BJJMuNRlq)
[測驗題目](https://hackmd.io/@sysprog/linux2022-quiz3)
## 測驗 1
### 題目
在 Linux 核心原始程式碼,[include/linux/bitfield.h](https://github.com/torvalds/linux/blob/master/include/linux/bitfield.h) 提及一個巨集 GENMASK,其作用是依據給定的範圍,產生連續的 bitmask,例如:
* `GENMASK(6, 4)` 產生 011100002
* `GENMASK(39, 21)` 產生 0x000000ffffe00000 (64 位元)
已知我們使用的微處理器架構為 64 位元,且 `unsigned long` 為 8 位元組寬度 (符合 LP64 資料模型),以下是可能的 `GENMASK` 巨集的定義:
```c
#define GENMASK(h, l) \
(((~0UL) >> (LEFT)) & ((~0UL) >> (l) << (RIGHT)))
```
請補完,使其行為符合預期。作答規範:
1. `LEFT` 和 `RIGHT` 應以最簡潔的形式撰寫,且符合[作業一](https://hackmd.io/@sysprog/linux2022-lab0)排版規範 (近似 Linux 核心程式碼排版風格)
2. `LEFT` 和 `RIGHT` 皆為表示式,可能包含常數
3. `LEFT` 和 `RIGHT` 皆不該包含小括號 (即 `(` 和 `)`)
### 作答
觀察 bitmask 的產生方式為將兩個 `~0UL` (即 64 位元皆為 1)進行相應的位移操作後,做 `AND` 操作,因此我們可以推斷位移操作的目的是將範圍外的位元變為 0 ,以下計算前後兩項所需清除的位元:
+ `(~0UL) >> (LEFT)`: 該項為右移,目的為清除範圍外的高位元,最高位元是第 `63` 位元,因此需要清除 `63-h` 個位元,故 `LEFT` = `63-h`
+ `(~0UL) >> (l) << (RIGHT)`: 該項為左移,目的為清除範圍外的低位元,最低位元是第 `0` 位元,因此需要清除 `l - 0` = `l` 個位元,故 `RIGHT` = `l`
\begin{align}
(\sim{0\text{UL}})>>(63-h) &= \overbrace{0\cdots0}^{63-h}\overbrace{1\cdots1}^{h+1}\\
(\sim{0\text{UL}})>>(l)<<(l) &= \overbrace{0\cdots0}^{l}\underbrace{1\cdots1}_{64-2l}\overbrace{0\cdots0}^{l}
\end{align}
\begin{array}{rrr}
&\overbrace{0\ \cdots\ 0}^{63-h}\overbrace{1\ \ \ \ \ \cdots\ \ \ 1}^{h+1} &\\
\cdot &\overbrace{0\cdots0}^{l}\overbrace{1\cdots1}^{64-2l}\overbrace{0\cdots0}^{l} &\\ \hline
\text{GENMASK(h, l)}\ = &\overbrace{0\cdots0}^{63-h}1\cdots1\overbrace{0\cdots0}^{l} &
\end{array}
:::warning
其中 `(~0UL) >> (l) << (RIGHT)` 的部分不太清楚為甚麼需要先右移再左移,範圍外的高位元應該在前一項就已經排除了,這邊的右移操作不知道有甚麼考量
:::
答案 :
+ `LEFT` = `63-h`
+ `RIGHT` = `l`
## 測驗 2
### 題目
考慮以下程式碼:
```c
struct foo;
struct fd {
struct foo *foo;
unsigned int flags;
};
enum {
FOO_DEFAULT = 0,
FOO_ACTION,
FOO_UNLOCK,
} FOO_FLAGS;
static inline struct fd to_fd(unsigned long v)
{
return (struct fd){EXP1, v & 3};
}
```
函式 fo_fd 接受指定的地址 v,填入一個 fd 結構體,並確保第一個成員的地址得以依據[〈你所不知道的 C 語言:記憶體管理、對齊及硬體特性〉](https://hackmd.io/@sysprog/c-memory)描述,對 4 個位元組進行向下對齊 (即 C++ Boost 描述的 [align_down](https://www.boost.org/doc/libs/1_78_0/doc/html/align/reference.html#align.reference.functions.align_down))。其中 struct foo; 運用[〈你所不知道的 C 語言:指標篇〉](https://hackmd.io/@sysprog/c-pointer)提及的 forward declaration 技巧,可在實作階段才提供具體 struct foo 的定義。
請補完,使其行為符合預期。作答規範:
1. `EXP1` 應以最簡潔的 C99 形式撰寫,符合作業一排版規範,且不該觸發編譯器警告訊息
2. `EXP1` 不得使用 `%` (modulo) 運算子
3. 當變數和常數進行運算時,變數應該出現前於常數,例如 `v | 1`
### 作答
依題目所要求, `v` 向下對齊到能被 4 整除的地址上,將 `v` 的 bit 0 跟 bit 1 清除便是 4 的倍數,使用位元遮罩來保留 bit 0, 1 以外的位元,即 `v & ~3`。
接著透過 `(struct foo *)` 轉型成 `foo` 結構體的指標存取該地址
答案:
+ `EXP1` = `(struct foo *)(v & ~3)`
## 測驗 3
### 題目
考慮以下程式碼,能將輸入的 8 位元無號整數逐一位元反轉,如同 LeetCode [190. Reverse Bits](https://leetcode.com/problems/reverse-bits/) 的描述。
```c
#include <stdint.h>
uint8_t rev8(uint8_t x)
{
x = (x >> 4) | (x << 4);
x = ((x & 0xCC) >> 2) | (EXP2);
x = ((x & 0xAA) >> 1) | (EXP3);
return x;
}
```
請補完,使其行為符合預期。作答規範:
1. `EXP2` 和 `EXP3` 應以最簡潔的 C99 形式撰寫,符合作業一排版規範,且不該觸發編譯器警告訊息
2. 當變數和常數進行運算時,變數應該出現前於常數,例如 `v | 1`
### 作答
觀察題目,得知該程式碼逐步交換位元,步驟如下:
+ 第一次交換: 將 4 位高位元與 4 位低位元交換,使用位移操作剛好可以將位元移至目標位置
```graphviz
digraph A{
subgraph iter{
original
[shape=none label=<
<table cellspacing="0" border="0">
<tr>
<td border="1" bgcolor="grey">7</td>
<td border="1" bgcolor="grey">6</td>
<td port="port0" border="1" bgcolor="grey">5</td>
<td border="1" bgcolor="grey">4</td>
<td border="1" bgcolor="white">3</td>
<td border="1" bgcolor="white">2</td>
<td port="port1" border="1" bgcolor="white">1</td>
<td border="1" bgcolor="white">0</td>
</tr>
</table>
>]
swap
[shape=none label=<
<table cellspacing="0" border="0">
<tr>
<td border="1" bgcolor="white">3</td>
<td border="1" bgcolor="white">2</td>
<td port="port2" border="1" bgcolor="white">1</td>
<td border="1" bgcolor="white">0</td>
<td border="1" bgcolor="grey">7</td>
<td border="1" bgcolor="grey">6</td>
<td port="port3" border="1" bgcolor="grey">5</td>
<td border="1" bgcolor="grey">4</td>
</tr>
</table>
>]
original:port0->swap:port3
original:port1->swap:port2
}
}
}
```
+ 第二次交換: 2 bits 為一組,相鄰組別兩兩交換,`(x & 0xCC)` 為圖中灰色的位元,剩餘的白色位元即為 `(x & ~0xCC)`=`(x & 0x33)`,對應的位移操作為左移 2 位。 `EXP2` = `(x & 0x33) << 2`
```graphviz
digraph A{
subgraph iter{
original
[shape=none label=<
<table cellspacing="0" border="0">
<tr>
<td border="1" bgcolor="grey">3</td>
<td port="port0" border="1" bgcolor="grey">2</td>
<td border="1" bgcolor="white">1</td>
<td port="port1" border="1" bgcolor="white">0</td>
<td border="1" bgcolor="grey">7</td>
<td port="port2" border="1" bgcolor="grey">6</td>
<td port="port1" border="1" bgcolor="white">5</td>
<td port="port3" border="1" bgcolor="white">4</td>
</tr>
</table>
>]
swap
[shape=none label=<
<table cellspacing="0" border="0">
<tr>
<td border="1" bgcolor="white">1</td>
<td port="port4" border="1" bgcolor="white">0</td>
<td border="1" bgcolor="grey">3</td>
<td port="port5" border="1" bgcolor="grey">2</td>
<td border="1" bgcolor="white">5</td>
<td port="port6" border="1" bgcolor="white">4</td>
<td border="1" bgcolor="grey">7</td>
<td port="port7" border="1" bgcolor="grey">6</td>
</tr>
</table>
>]
}
original:port0->swap:port5
original:port1->swap:port4
original:port2->swap:port7
original:port3->swap:port6
}
}
```
+ 第三次交換: 1 bit 為一組,相鄰組別兩兩交換,`(x & 0xAA)` 為圖中灰色的位元,剩餘的白色位元即為 `(x & ~0xAA)`=`(x & 0x55)`,對應的位移操作為左移 1 位。 `EXP3` = `(x & 0x55) << 1`
```graphviz
digraph A{
subgraph iter{
original
[shape=none label=<
<table cellspacing="0" border="0">
<tr>
<td port="port0" border="1" bgcolor="grey">1</td>
<td port="port1" border="1" bgcolor="white">0</td>
<td port="port2" border="1" bgcolor="grey">3</td>
<td port="port3" border="1" bgcolor="white">2</td>
<td port="port4" border="1" bgcolor="grey">5</td>
<td port="port5" border="1" bgcolor="white">4</td>
<td port="port6" border="1" bgcolor="grey">7</td>
<td port="port7" border="1" bgcolor="white">6</td>
</tr>
</table>
>]
swap
[shape=none label=<
<table cellspacing="0" border="0">
<tr>
<td port="port8" border="1" bgcolor="white">0</td>
<td port="port9" border="1" bgcolor="grey">1</td>
<td port="port10" border="1" bgcolor="white">2</td>
<td port="port11" border="1" bgcolor="grey">3</td>
<td port="port12" border="1" bgcolor="white">4</td>
<td port="port13" border="1" bgcolor="grey">5</td>
<td port="port14" border="1" bgcolor="white">6</td>
<td port="port15" border="1" bgcolor="grey">7</td>
</tr>
</table>
>]
}
original:port0->swap:port9
original:port1->swap:port8
original:port2->swap:port11
original:port3->swap:port10
original:port4->swap:port13
original:port5->swap:port12
original:port6->swap:port15
original:port7->swap:port14
}
}
```
答案 :
+ `EXP2` = `(x & 0x33) << 2`
+ `EXP3` = `(x & 0x55) << 1`
## 測驗 4
### 題目
延伸 [〈你所不知道的 C 語言:前置處理器應用篇〉](https://hackmd.io/@sysprog/c-preprocessor),我們嘗試將 foreach 巨集 予以擴充,使得支援以下寫法:
```c
int i;
foreach_int(i, 0, -1, 100, 0, -99) {
printf("i is %i\n", i);
}
const char *p;
foreach_ptr(p, "Hello", "world") {
printf("p is %s\n", p);
}
```
預期輸出如下:
```
i is 0
i is -1
i is 100
i is 0
i is -99
p is Hello
p is world
```
對應的巨集定義:
```c
#include <assert.h>
#define _foreach_no_nullval(i, p, arr) \
assert((i) >= sizeof(arr) / sizeof(arr[0]) || (p))
#define foreach_int(i, ...) \
for (unsigned _foreach_i = (((i) = ((int[]){__VA_ARGS__})[0]), 0); \
_foreach_i < sizeof((int[]){__VA_ARGS__}) / sizeof(int); \
(i) = ((int[]){__VA_ARGS__, 0})[EXP4])
#define foreach_ptr(i, ...) \
for (unsigned _foreach_i = \
(((i) = (void *) ((typeof(i)[]){__VA_ARGS__})[0]), 0); \
(i); (i) = (void *) ((typeof(i)[]){__VA_ARGS__, \
NULL})[EXP5], \
_foreach_no_nullval(_foreach_i, i, \
((const void *[]){__VA_ARGS__})))
```
請補完,使其行為符合預期。作答規範:
1. `EXP4` 和 `EXP5` 應以最簡潔的 C99 形式撰寫,符合作業一排版規範,且不該觸發編譯器警告訊息
2. 不該出現小括號 (即 ( 和 ))
### 作答
題目中 `foreach` 的功能為走訪第一個除外的所有參數,並將其值指派給第一個參數
在 `foreach_int` 巨集中,分析 `for (clause-1; exp-2; exp-3)` 陳述句中的三個部分:
+ `clause-1` : 初始化 `i` 和 `_foreach_i`
+ `exp-2` : 判斷 `_foreach_i` 是否超過可走訪大小
+ `exp-3` : 更新 `i` 和 `_foreach_i`
我們需要補上更新的部分, `i` 需要更新為下一個參數, `(int[]){__VA_ARGS__, 0}` 是將欲走訪之參數視作陣列,透過 array subscript 存取下一個元素,利用 `++_foreach_i` ,同時更新 `_for_each_i`。故 `EXP4` = `++_foreach_i`,`__VA_ARGS__` 後面補上 `0` 可以避免因前置遞增運算子而存取到錯誤的記憶體空間
`foreach_iptr` 巨集中與 `foreach_int` 類似,走訪各個字串,其中 `_foreach_no_nullval` 巨集的功能為檢查空字串。 `EXP5` 的功能與 `EXP4` 相同,皆是在走訪下一個元素的同時更新作為索引值的變數
答案 :
+ `EXP4` = `++_foreach_i`
+ `EXP5` = `++_foreach_i`
## 測驗 5
### 題目
針對 LeetCode [29. Divide Two Integers](https://leetcode.com/problems/divide-two-integers/),以下是可能的實作:
```c
#include <limits.h>
int divide(int dividend, int divisor)
{
int signal = 1;
unsigned int dvd = dividend;
if (dividend < 0) {
signal *= -1;
dvd = ~dvd + 1;
}
unsigned int dvs = divisor;
if (divisor < 0) {
signal *= -1;
dvs = ~dvs + 1;
}
int shift = 0;
while (dvd > (EXP6))
shift++;
unsigned int res = 0;
while (dvd >= dvs) {
while (dvd < (dvs << shift))
shift--;
res |= (unsigned int) 1 << shift;
EXP7;
}
if (signal == 1 && res >= INT_MAX)
return INT_MAX;
return res * signal;
}
```
請補完,使其行為符合預期。作答規範:
1. `EXP6` 和 `EXP7` 應以最簡潔的 C99 形式撰寫,符合[作業一](https://hackmd.io/@sysprog/linux2022-lab0)排版規範,且不該觸發編譯器警告訊息
2. 不該出現小括號 (即 ( 和 ))
### 作答
先觀察 `EXP6` 所在迴圈的功用,推測為計算除數對齊被除數所需要的最大位移量,當位移後的除數大於被除數便跳出迴圈,故 `EXP6` = `dvs << shift`
再來觀察 `EXP7` 所在迴圈的功用,推測為計算商數
```c=
while (dvd >= dvs) {
while (dvd < (dvs << shift))
shift--;
res |= (unsigned int) 1 << shift;
EXP7;
}
```
+ 第 2 ~ 3 行 : 調整除數的位移量,找出能使被除數減去位移後的除數的對應位移量
+ 第 4 行 : 將 `1` 填入商數中相對應的位元
+ 第 5 行 : 這邊需要更新被除數,為被除數與位移後的除數兩者之差,故 `EXP7` = `dvd -= dvs << shift`
`EXP6` = `dvs << shift`
`EXP7` = `dvd -= dvs << shift`
## 測驗 6
### 題目
針對 LeetCode [149. Max Points on a Line](https://leetcode.com/problems/max-points-on-a-line/),以下是可能的實作:
```c
#include <stdbool.h>
#include "list.h"
struct Point {
int x, y;
};
struct point_node {
int p1, p2;
struct list_head link;
};
static bool can_insert(struct list_head *head, int p1, int p2)
{
struct point_node *pn;
list_for_each_entry (pn, head, link)
return EXP8;
return true;
}
static int gcd(int x, int y)
{
while (y) {
int tmp = y;
y = x % y;
x = tmp;
}
return x;
}
static int maxPoints(struct Point *points, int pointsSize)
{
if (pointsSize <= 2)
return pointsSize;
int i, j, slope_size = pointsSize * pointsSize / 2 + 133;
int *dup_cnts = malloc(pointsSize * sizeof(int));
int *hori_cnts = malloc(pointsSize * sizeof(int));
int *vert_cnts = malloc(pointsSize * sizeof(int));
int *slope_cnts = malloc(slope_size * sizeof(int));
memset(hori_cnts, 0, pointsSize * sizeof(int));
memset(vert_cnts, 0, pointsSize * sizeof(int));
memset(slope_cnts, 0, slope_size * sizeof(int));
for (i = 0; i < pointsSize; i++)
dup_cnts[i] = 1;
struct list_head *heads = malloc(slope_size * sizeof(*heads));
for (i = 0; i < slope_size; i++)
INIT_LIST_HEAD(&heads[i]);
for (i = 0; i < pointsSize; i++) {
for (j = i + 1; j < pointsSize; j++) {
if (points[i].x == points[j].x)
hori_cnts[i]++, hori_cnts[j]++;
if (points[i].y == points[j].y)
vert_cnts[i]++, vert_cnts[j]++;
if (points[i].x == points[j].x && points[i].y == points[j].y)
dup_cnts[i]++, dup_cnts[j]++;
if (points[i].x != points[j].x && points[i].y != points[j].y) {
int dx = points[j].x - points[i].x;
int dy = points[j].y - points[i].y;
int tmp = gcd(dx, dy);
dx /= tmp;
dy /= tmp;
int hash = dx * dy - 1333 * (dx + dy);
if (hash < 0)
hash = -hash;
hash %= slope_size;
if (can_insert(&heads[hash], i, j)) {
struct point_node *pn = malloc(sizeof(*pn));
pn->p1 = i;
pn->p2 = j;
EXP9;
}
}
}
}
for (i = 0; i < slope_size; i++) {
int index = -1;
struct point_node *pn;
list_for_each_entry (pn, &heads[i], link) {
index = pn->p1;
slope_cnts[i]++;
}
if (index >= 0)
slope_cnts[i] += dup_cnts[index];
}
int max_num = 0;
for (i = 0; i < pointsSize; i++) {
if (hori_cnts[i] + 1 > max_num)
max_num = hori_cnts[i] + 1;
if (vert_cnts[i] + 1 > max_num)
max_num = vert_cnts[i] + 1;
}
for (i = 0; i < slope_size; i++) {
if (slope_cnts[i] > max_num)
max_num = slope_cnts[i];
}
return max_num;
}
```
請補完,使其行為符合預期。作答規範:
1. `EXP8` 和 `EXP9` 應以最簡潔的 C99 形式撰寫,符合作業一排版規範,且不該觸發編譯器警告訊息
2. `EXP8` 不該出現小括號 (即 `(` 和 `)`)
3. `EXP9` 為包含 `list_` 開頭巨集的單一敘述
### 作答
根據 `EXP8` 所在的函式名稱,猜測該函式的功能為判斷座標 (p1, p2) 是否符合某種條件,可以插入該位置的鏈結串列,觀察尋找呼叫該函式的程式碼段落,判斷此條件為
## 測驗 7
### 題目
考慮 `ilog32` 函式可針對給定的 32 位元無號數,計算扣除開頭的 0,最少需要多少位元才可儲存 (the minimum number of bits required to store an unsigned 32-bit value without any leading zero bits),以下是一個可能的實作:
```c
int ilog32(uint32_t v)
{
int ret = v > 0;
int m = (v > 0xFFFFU) << 4;
v >>= m;
ret |= m;
m = (v > 0xFFU) << 3;
v >>= m;
ret |= m;
m = (v > 0xFU) << 2;
v >>= m;
ret |= m;
m = EXP10;
v >>= m;
ret |= m;
EXP11;
return ret;
}
```
請補完,使其行為符合預期。作答規範:
1. `EXP10` 和 `EXP11` 應以最簡潔的 C99 形式撰寫,符合作業一排版規範,且不該觸發編譯器警告訊息
2. `EXP11` 不該出現小括號 (即 ( 和 ))
3. `EXP10` 和 `EXP11` 皆包含 > 運算子
## 測驗 8
### 題目
考慮以下 C++ 二元搜尋樹的程式:
:::spoiler 原本程式碼
```c
typedef struct tnode *tree;
struct tnode {
int data;
tnode *left;
tnode *right;
tnode(int d)
{
data = d;
left = right = 0;
}
};
void remove_data(tree &t, int d)
{
tnode *p = t;
tnode *q;
while (p != 0 && p->data != d) {
if (d < p->data) {
q = p;
p = p->left;
} else {
q = p;
p = p->right;
}
}
if (p != 0) {
if (p->left == 0)
if (p == t)
t = p->right;
else if (p == q->left)
q->left = p->right;
else
q->right = p->right;
else if (p->right == 0)
if (p == t)
t = p->left;
else if (p == q->left)
q->left = p->left;
else
q->right = p->left;
else {
tnode *r = p->right;
while (r->left) {
q = r;
r = r->left;
}
p->data = r->data;
if (r == p->right)
p->right = r->right;
else
q->left = r->right;
p = r;
}
delete p;
}
}
```
:::
函式 `remove_data` 嘗試將指定的資料,自給定的二元搜尋樹移除,但原本的 `remove_data` 過於冗長,於是我們可善用 indirect pointer 改寫為以下:
```c
void remove_data(tree &t, int d)
{
tnode **p = &t;
while (*p != 0 && (*p)->data != d) {
if (d < (*p)->data)
EXP12;
else
EXP13;
}
tnode *q = *p;
if (!q)
return;
if (!q->left)
*p = q->right;
else if (!q->right)
*p = q->left;
else {
tnode **r = &q->right;
while ((*r)->left)
r = EXP14;
q->data = (*r)->data;
q = *r;
*r = q->right;
}
delete q;
}
```
請補完,使其行為符合預期。作答規範:
`EXP12`, `EXP13` 和 `EXP14` 應以最簡潔的 C99 形式撰寫,符合作業一排版規範,且不該觸發編譯器警告訊息,並善用 indirect pointer 的技巧來撰寫
`EXP12`, `EXP13` 和 `EXP14` 皆不該出現 ;
## 測驗 9
### 題目
考慮以下進行記憶體地址對齊 (alignment) 的巨集:
```c
/* maximum alignment needed for any type on this platform, rounded up to a
power of two */
#define MAX_ALIGNMENT 16
/* Given a size, round up to the next multiple of sizeof(void *) */
#define ROUND_UP_TO_ALIGNMENT_SIZE(x) \
(((x) + MAX_ALIGNMENT - MMM) & ~(NNN))
```
作用是向上對齊 (roundup),請補完程式碼,使其行為符合預期。作答規範:
* `MMM` 是常數
* `NNN` 是表示式
* `MMM` 和 `NNN` 皆不得出現小括號 (即 ( 和 ))
* 符合作業一的排版和程式碼風格規範
* 以符合 C99 的最精簡形式撰寫
## 測驗 10
### 題目
考慮以下巨集可得到二個表示式進行整數除法操作時,最接近的整數值:
```c
#define DIVIDE_ROUND_CLOSEST(x, divisor) \
({ \
typeof(x) __x = x; \
typeof(divisor) __d = divisor; \
(((typeof(x)) -1) > 0 || ((typeof(divisor)) -1) > 0 || \
(((__x) > 0) == ((__d) > 0))) \
? ((RRR) / (__d)) \
: ((SSS) / (__d)); \
})
```
注意: 當除數 (即 divisor) 為負值時,行為沒有定義。
參考執行結果:
* `DIVIDE_ROUND_CLOSEST(7, 3) = 2`
* `DIVIDE_ROUND_CLOSEST(6, 3) = 2`
* `DIVIDE_ROUND_CLOSEST(5, 3) = 2`
作答規範:
* 符合作[業一](https://hackmd.io/@sysprog/linux2022-lab0)的排版和程式碼風格規範
* `RRR` 和 `SSS` 為表示式,且都包含 `(__x)` 和 `(__d)` (注意小括號)
* `RRR` 和 `SSS` 限用 `+`, `-`, `>>`, `<<` 這幾個運算子,可搭配小括號,並以符合 C99 的最精簡形式撰寫
* 變數在 `RRR` 和 `SSS` 出現的順序 (從左到右),必定是 `__x` 先於 `__d`
* 請補完程式碼,使其行為符合預期。
## 測驗 11
### 題目
考慮以下計算整數開平方根的實作:
```c
static inline unsigned long fls(unsigned long word)
{
int num = 64 - 1;
if (!(word & (~0ul << 32))) {
num -= 32;
word <<= 32;
}
if (!(word & (~0ul << (64 - 16)))) {
num -= 16;
word <<= 16;
}
if (!(word & (~0ul << (64 - 8)))) {
num -= 8;
word <<= 8;
}
if (!(word & (~0ul << (64 - 4)))) {
num -= 4;
word <<= 4;
}
if (!(word & (~0ul << (64 - 2)))) {
num -= 2;
word <<= 2;
}
if (!(word & (~0ul << (64 - 1))))
num -= 1;
return num;
}
unsigned long i_sqrt(unsigned long x)
{
unsigned long b, m, y = 0;
if (x <= 1)
return x;
m = 1UL << (fls(x) & ~1UL);
while (m) {
b = y + m;
XXX;
if (x >= b) {
YYY;
y += m;
}
ZZZ;
}
return y;
}
```
`i_sqrt` 函式的作用等同於 `floor(sqrt(x))`
作答規範:
* 符合作業一的排版和程式碼風格規範
* `XXX`, `YYY` 和 `ZZZ` 都是敘述,不得呼叫任何函式,且不包含 ; 字元
* 只能使用 `=`, `+=`, `-=`, `>>=`, `<<=` 等運算子,且不得出現小括號 (即 ( 和 ))
* 以符合 C99 的最精簡形式撰寫