# 2025q1 Homework4 (quiz3+4)
contributed by <`bevmmf`>
## 預期目標
- [ ] 檢驗學員對於 bitwise 操作及 rbtree 的熟悉程度
- [ ] 檢驗學員對於 CS:APP 第二章的掌握:別忘了課程指定的教科書
- [ ] 引導學員接觸 Linux 核心原始程式碼及解說應用案例
## quiz3
### 測驗一
補完程式碼
- `AAAA`:`(n + d - 1) / d`
- `BBBB`:`0x7fffffff`
- `CCCC`:`n + m`
- `DDDD`:`1`
- `EEEE`:`mpi_gcd(rop, op2, r)`
:::success
解釋上述程式碼運作原理
:::
```c
#include <inttypes.h> //support int32_t, uint64_t
#include <stdint.h> //support uint32_t, int64_t
/* mpi: Multi-Precision Integers */
typedef struct {
uint32_t *data;
size_t capacity;
} mpi_t[1];
typedef size_t mp_bitcnt_t;
```
- Essential Knowledge
- `size_t` : 一個專為表示「大小」或「數量」設計的unsignd int類型
- 根據系統為 32bits或64bits
- 必為非負
此為宣告本題MPI的結構,由 `capacity` 段 uint32_t 類型 `data` 組合成多精度的整數進而能描述規模大的int
- `mpi_t[1]` 表示單元素array,目的是傳遞pointer
`mpi_init` : 初始化一mpi_t使capacity為0,data指向NULL
`mpi_clear` : free 釋放一mpi_t之data
```c
void mpi_enlarge(mpi_t rop, size_t capacity)
{
if (capacity > rop->capacity) {
size_t min = rop->capacity;
rop->capacity = capacity;
rop->data = realloc(rop->data, capacity * 4);
if (!rop->data && capacity != 0) {
fprintf(stderr, "Out of memory (%zu words requested)\n", capacity);
abort();
}
for (size_t n = min; n < capacity; ++n)
rop->data[n] = 0;
}
}
```
`mpi_enlarge` : 擴充一mpi_t的data段數,若傳參capacity > 該mpi之capacity才運作。運作時由realloc重新分配(傳參capacity*4)bytes,然後將新擴充的段數data都設為0。若分配失敗則輸出錯誤資訊然後abort。
```c
void mpi_compact(mpi_t rop)
{
size_t capacity = rop->capacity;
if (rop->capacity == 0)
return;
for (size_t i = rop->capacity - 1; i != (size_t) -1; --i) {
if (rop->data[i])
break;
capacity--;
}
assert(capacity != (size_t) -1);
rop->data = realloc(rop->data, capacity * 4);
rop->capacity = capacity;
if (!rop->data && capacity != 0) {
fprintf(stderr, "Out of memory (%zu words requested)\n", capacity);
abort();
}
}
```
`mpi_compact` : 壓縮一mpit的記憶體使用,去除末尾的零元素,調整capacity。從data array末尾倒序檢查是否為0,為0則使capcity-1,直到不為0退出。此時退出後之capacity即為皆非0值之data段數。最後利用realloc分配該capacity段數4 bytes的空間。分配失敗則終止。
:::info
- 為什麼需要 `(size_t) -1`?
- 此為將 -1 強制轉型為size_t值(0xFFFFFFFF或0xFFFFFFFFFFFFFFFF)
- 當 i = 0 - -i 時i會變成size_t最大值也就是(size_t),確保迴圈不會無限執行
- `%zu` 專為用來輸出 size_t 的值
- 為什麼不用 %d 或 %u?
- %d 用於有符號整數(int),而 size_t 是無符號的,可能導致輸出錯誤(特別是當值很大時)。
- %u 用於無符號 int,但 size_t 的大小可能與 int 不同(例如在 64 位系統上,size_t 可能是 64 位,而 int 是 32 位)。使用 %u 可能導致截斷或未定義行為。
:::
```c
/* ceiling division without needing floating-point operations. */
static size_t ceil_div(size_t n, size_t d)
{
return (n + d - 1) / d;
}
#define INTMAX 0x7fffffff
void mpi_set_u64(mpi_t rop, uint64_t op)
{
size_t capacity = ceil_div(64, 31);
mpi_enlarge(rop, capacity);
for (size_t n = 0; n < capacity; ++n) {
rop->data[n] = op & INTMAX;
op >>= 31;
}
for (size_t n = capacity; n < rop->capacity; ++n)
rop->data[n] = 0;
}
```
`ceil_div` : 取天花板數(向上取整)
`mpi_set_u64` : 將 uint64_t 轉成 mpi_t
- 操作
- 先用 ceil_div 對 64 與 31 取得分段數 capacity
- 再將傳參mpi擴充到該capacity
- 最後將 uint64_t 的data以31個1(0x7FFFFFFF,$2^{31}-1$)為一段作mask放進傳參mpi
- 若傳參mpi capcity過大則將高段數設為0
:::info
為啥用 31 bit 作為一段長度?
因為若用 32 bit 加法或乘法進位(carry)處理會變複雜
- 例如:兩個最大 32 位元數相加:0xFFFFFFFF + 0xFFFFFFFF = 0x1FFFFFFFE(33 位元)
- 兩個 31 位元數相加的最大結果是 0x7FFFFFFF + 0x7FFFFFFF = 0xFFFFFFFE(32 位元),正好能放入一個 uint32_t 而不溢位
:::
```c
static void mpi_mul_naive(mpi_t rop, const mpi_t op1, const mpi_t op2)
{
size_t capacity = op1->capacity + op2->capacity;
mpi_t tmp;
mpi_init(tmp);
mpi_enlarge(tmp, capacity);
for (size_t n = 0; n < tmp->capacity; ++n)
tmp->data[n] = 0;
for (size_t n = 0; n < op1->capacity; ++n) {
for (size_t m = 0; m < op2->capacity; ++m) {
uint64_t r = (uint64_t) op1->data[n] * op2->data[m];
uint64_t c = 0;
for (size_t k = n + m; c || r; ++k) {
if (k >= tmp->capacity)
mpi_enlarge(tmp, tmp->capacity + 1);
tmp->data[k] += (r & INTMAX) + c;
r >>= 31;
c = tmp->data[k] >> 31;
tmp->data[k] &= INTMAX;
}
}
}
mpi_set(rop, tmp);
mpi_compact(rop);
mpi_clear(tmp);
}
```
`mpi_mul_naive` : mpi乘法
- 運作
做直式乘法
- 外圈 : op1段數遍歷
- 中圈 : op2段數遍歷
- 內圈 : 處理carry加到data[k+1]
:::info
是否需要以下部分?
1.
```c
if (k >= tmp->capacity)
mpi_enlarge(tmp, tmp->capacity + 1);
```
不需要 => n段*m段不會大於n+m段
2.
```c
tmp->data[k] &= INTMAX;
```
不需要 => 因data大小即為INTMAX
:::
```c
//計算多精度整數 n 除以 d 的商 q 和餘數 r (即 n = d * q + r)
void mpi_fdiv_qr(mpi_t q, mpi_t r, const mpi_t n, const mpi_t d)
{
mpi_t n0, d0;
mpi_init(n0);
mpi_init(d0);
mpi_set(n0, n);//copy
mpi_set(d0, d);
if (mpi_cmp_u32(d0, 0) == 0) {
fprintf(stderr, "Division by zero\n");
abort();
}
mpi_set_u32(q, 0);
mpi_set_u32(r, 0);
size_t start = mpi_sizeinbase(n0, 2) - 1;
for (size_t i = start; i != (size_t) -1; --i) {
mpi_mul_2exp(r, r, 1);
if (mpi_testbit(n0, i) != 0)
mpi_setbit(r, 0);
if (mpi_cmp(r, d0) >= 0) {
mpi_sub(r, r, d0);
mpi_setbit(q, i);
}
}
mpi_clear(n0);
mpi_clear(d0);
}
```
使用二進制長除法
- 以下為運作流程 :
### 初始化
- 被除數:n0 = 1101(十進位 13)
- 除數:d0 = 11(十進位 3)
- 商:q = 0
- 餘數:r = 0
- 起始索引:`start = mpi_sizeinbase(n0, 2)` - 1 = 4 - 1 = 3(最高位索引)
### 逐位處理
從最高位(i = 3)到最低位(i = 0)逐一處理被除數的每一位。
#### 步驟 1:i = 3(最高位,n0[3] = 1)
- 餘數左移:r = 0 左移 1 位 → r = 0
- 附加位元:n0[3] = 1,設 r[0] = 1 → r = 1
- 比較與減法:r = 1 < d0 = 3,不減,設 q[3] = 0
- 結果:r = 1, q = 0000
#### 步驟 2:i = 2(n0[2] = 1)
- 餘數左移:r = 1 左移 1 位 → r = 10(十進位 2)
- 附加位元:n0[2] = 1,設 r[0] = 1 → r = 11(十進位 3)
- 比較與減法:r = 3 >= d0 = 3,減去 d0:r = 3 - 3 = 0,設 q[2] = 1
- 結果:r = 0, q = 0100
#### 步驟 3:i = 1(n0[1] = 0)
- 餘數左移:r = 0 左移 1 位 → r = 0
- 附加位元:n0[1] = 0,r 不變 → r = 0
- 比較與減法:r = 0 < d0 = 3,不減,設 q[1] = 0
- 結果:r = 0, q = 0100
#### 步驟 4:i = 0(最低位,n0[0] = 1)
- 餘數左移:r = 0 左移 1 位 → r = 0
- 附加位元:n0[0] = 1,設 r[0] = 1 → r = 1
- 比較與減法:r = 1 < d0 = 3,不減,設 q[0] = 0
- 結果:r = 1, q = 0100
### 最終結果
- 商:q = 0100(十進位 4)
- 餘數:r = 1(十進位 1)
### 驗證
驗證結果:
13 = 3 × 4 + 1
```c
void mpi_gcd(mpi_t rop, const mpi_t op1, const mpi_t op2)
{
if (mpi_cmp_u32(op2, 0) == 0) {
mpi_set(rop, op1);
return;
}
mpi_t q, r;
mpi_init(q);
mpi_init(r);
mpi_fdiv_qr(q, r, op1, op2);
mpi_gcd(rop, op2, r);
mpi_clear(q);
mpi_clear(r);
}
```
使用輾轉相除法
當發生整除時(下一次r=0,guard clause發生return),將輸出rop設為當時被除數op1
:::success
這項實作依賴遞迴呼叫,請避免使用並降低記憶體開銷
:::
我將原來遞迴修改成以迭代實作
```c
void mpi_gcd(mpi_t rop, const mpi_t op1, const mpi_t op2)
{
if (mpi_cmp_u32(op1, 0) == 0 || mpi_cmp_u32(op2, 0) == 0) {
fprintf(stderr, "Both operands are zero, GCD undefined\n");
return;
}
mpi_t opa, opb, q, r;
mpi_init(opa); // Dividend
mpi_init(opb); // Divisor
mpi_init(q);
mpi_init(r);
mpi_set(opa,op1);
mpi_set(opb,op2);
while(mpi_cmp_u32(opb, 0) != 0){
mpi_fdiv_qr(q, r, opa, opb);
mpi_set(opa,opb);
mpi_set(opb,r);
}
mpi_set(rop,opa);
mpi_clear(q);
mpi_clear(r);
mpi_clear(opa);
mpi_clear(opb);
}
```
:::success
改進最大公因數的運算效率
:::
### 測驗二
- `AAAA`:`(x) ^ (mask)`
- `BBBB`:`mask << i`
- `CCCC`:`*asrc`
### 測驗三
- `AAAA`:`ENV_RUNNABLE`
- `BBBB`:`attempts + 1`
- `CCCC`:`coro_shedule`
- `DDDD`:`yield`
## quiz4
### 測驗一
- `AAAA`:`0xc38d26c4`
- `BBBB`:`0xd3d3e1ab`
- `CCCC`:`0xe330a81a`
- `DDDD`:`0xf36e6f75`
### 測驗二
- `AAAA`:`0x7FFF`
- `BBBB`:`0x80000000`
- `CCCC`:`0x7FFFFFFF`
- `DDDD`:`0x80000000`
- `EEEE`:`0x7FFFFFFF`
- `FFFF`:`input & 0xFF`
- `GGGG`:`Q15_MIN`
- `HHHH`:`Q15_MAX`
### 測驗三
- `IIII`:`max_fd`
- `JJJJ`:`&rfds`
## Reference
- [2025q1 第 3 週測驗題](https://hackmd.io/@sysprog/linux2025-quiz3)
- [2025q1 第 4 週測驗題](https://hackmd.io/@sysprog/linux2025-quiz4)