# 2025q1 Homewor4
contributed by < ibat10clw >
{%hackmd NrmQUGbRQWemgwPfhzXj6g %}
## [2025q1](https://hackmd.io/@sysprog/linux2025-quiz3) 第 3 週測驗題
### 測驗 1
題目實作的是一個儲存 bit 數可隨需求增長的計算系統。
第一個函式 `ceil_div`,要求不使用浮點數達成向上取整的效果
```c
static size_t ceil_div(size_t n, size_t d)
{
return (n + d - 1) / d/*AAAA*/;
}
```
觀察 `(n + d) / d` 和 `n / d` 兩者的結果相差為 1,但考慮 `n % d == 0` 的狀況,不需要將結果向上調整,因此使用 `(d - 1)` 來調整 n 的值,可以使 `n % d == 0` 時候不會進位到下一個區間。
接著是 `INTMAX` 的巨集,可以觀察到在下面的程式中有使用到此巨集
```c
void mpi_set_u32(mpi_t rop, uint32_t op)
{
size_t capacity = ceil_div(32, 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;
}
```
從 `capacity` 的計算可以得知,此處永遠會是 2,代表 `mpi` 結構體中的 `uint32_t *data;` 很可能無法存滿 32 bits 的資料
且結合 `op >>= 31` 的操作,可以發現 `rop->data[n]` 每次都寫入 31 個 bits,因此得出此處的 `INTMAX` 應該為取出 lower 31 bits 的巨集。
`#define INTMAX 0x7fffffff/*BBBB*/`
`CCCC` 的填空出現在 `mpi_mul_naive` 函式中,其內部有個三層的 `for` 迴圈
```c
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 =/*CCCC*/; 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` 中的每一筆紀錄,嘗試做長乘法運算
```
123
x 456
-------------
738
6150
49200
```
可以觀察上方的直式,每輪計算時須計算出乘積,之後與先前的結果相加,考慮最大的情況若是 `999 * 999` 這種組合,最終的位數會需要兩者的長度相加,因此 `CCCC` 為 `n + m`
接下來觀察除法實作,此處使用長除法的方式,計算 `mpi` 中的每一個位元
```c
/* Computes the quotient (q) and remainder (r) of n / d. */
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);
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/*DDDD*/);
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);
}
```
`DDDD` 處理的是餘數的部份,考慮長除法中的一次計算
```
3
-----------
3|105
| 9
-----------
1
```
這個餘數 1 會跟被除數的下一位數做組合,所以此處的 1 應該視為 10,接著加入 5,下一輪改為計算 `15 / 3`,這是十進位的一個舉例。
對應至二進位同樣也需要將餘數的權重做調整,對應該乘上的數值為 2,所以使用 `mpi_mul_2exp`,將 `mpi` 左移 1 個位元,作為乘 2 的等價操作。
最後的函式為最大公因數的計算,以遞迴處理
```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);/*EEEE*/
mpi_clear(q);
mpi_clear(r);
}
```
`EEEE` 之處做的事情為 `gcd(a, b) = gcd(b, a % b)` 的操作,所以 `rop` 不變而 `op2` 應取代原 `op1` 的位置,`op2` 則由 `a % b` 的餘數取代。
根據 `mpi_fdiv_qr` 的輸出會將商數和餘數分別存在 `q` 和 `r`,輸入的兩個運算元 `op1` 以及 `op2` 不作更動,因此接下來的遞迴呼叫取出 `op2` 以及商數 `r` 為下一輪計算的目標。