# 2025q1 Homework5 (assessment)
contributed by < `qianzsh` >
## 閱讀〈[因為自動飲料機而延畢的那一年](https://www.opasschang.com/docs/the-story-of-auto-beverage-machine-1)〉的啟發
- 人不付出犧牲,就得不到任何回報。如果要得到什麼,就必須付出同等的代價,這就是鍊金術的基本原則,等價交換。當時我們深信著,這就是這世界的真理。------《鋼之鍊金術師》
回顧這堂課程中所學,我發現跟文章中的故事有所相似,都是注重細節以及在解決真實世界發生的問題。目前為止,我讀完 linux 書的第一章,以及完成部份 lab0-c。
:::danger
為何你宣稱閱讀後,僅列出隻字片語呢?你沒有其他收穫嗎?
:::
## 2025q1 第 6 週測驗 2
### 數學理解
程式碼將 $\log_{2}(a)$ 的 $a$ 拆解成多個 $2$ 的相乘,再乘以一個小於 $2$ 的數 $b$ ,可以寫成:
$$
a = 2^1 \times 2^1 \times 2^1 \times b
$$
之後改寫 $\log_{2}(a)$ 裡的 $a$ ,再利用 $\log$ 特性,寫成:
$$
\log_{2}(a) = \log_{2}(2^1 \times 2^1 \times 2^1 \times b) = log_{2}(2) + log_{2}(2) + log_{2}(2) + log_{2}(b)
$$
我們可以透過以上的等式操作得到 $a$ 取 $\log_{2}$ 後的整數部份加上 $\log_{2}(b)$,$\log_{2}(b)$ 是 $a$ 取 $\log_{2}$ 後的小數部份,這裡可以把 $b$ 替換成 $b = (b^2)^\frac{1}{2} = (c)^\frac{1}{2}$ ,所以 $\log_{2}(b) = \log_{2}((b^2)^\frac{1}{2}) = \dfrac{1}{2}\log_{2}(b^2) = \dfrac{1}{2}\log_{2}(c)$ ,若 $c >2$ 則可以用上述的方法取得整數與小數部份,否則 $c = (c^2)^\frac{1}{2} = (d)^\frac{1}{2}$ ,以此往復循環直到遞迴深度達到停止條件。
:::danger
不用列出參考題解,專注在程式碼本體和你的認知上。
:::
程式碼先是透過判斷式 num >= base 判斷是否還有整數部份,若判斷為是則結果加一並對 num/base 繼續遞迴計算,累加出整數部分,反之則透過 for 迴圈以及遞迴來取得 log 後的小數部份,而變數 acc 決定遞迴深度。
### 用 Tail recursion 方式改寫
```c
double logarithm(double base, double num, double result, double weight,int acc)
{
/* Stop recursion once we have reached the desired depth */
if (acc <= 0)
return result;
/* If num is large enough to "remove" one whole unit of log at once */
if (num >= base){
result += weight;
return logarithm(base, num / base, result, weight, acc - 1);
}
/* Otherwise, multiply 'num' by itself 'base' times
* (which is effectively num^base).
*/
double tmp = 1.0;
for (int i = 0; i < (int) base; i++)
tmp *= num;
weight /= base;
return logarithm(base, tmp, result, weight, acc - 1);
}
```
閱讀「[你所不知道的 C 語言:遞迴呼叫篇](https://hackmd.io/@sysprog/c-recursion)」後,用 Tail recursion 這一種特殊形式的遞迴對原程式進行改寫,需要注意的是,GCC/Clang 要編譯器開啟最佳化才會做 tail recursion。
:::danger
注意精準的用語,參見[資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary)
:::
### 用定點數改寫
用 Q16.16 改寫,在過程中我注意到從浮點數變換到定點數是不能直接做位元位移;
在 C99 規格書 6.5.7 Bitwise shift operators 第 2 節指出
> Each of the operands shall have integer type.
限制只有整數型別能進行 bitwise 操作;
:::danger
為何不能對浮點數進行 bitwise 操作?列出相關 C 語言規格書的描述。
:::
考量 IEEE 754 浮點數的儲存格式包含符號位元、指數與尾數,進行位元操作的方式與整數不同,且 c語言並沒有對浮點數的位元操作進行定義,因此以下實做使用浮點數的乘除法進行運算。
```c
#include <stdio.h>
#include <stdint.h>
#define FIXED_SHIFT 16
#define TO_FIXED(x) ((int32_t)((x) * (1 << FIXED_SHIFT)))
#define TO_DOUBLE(x) ((double)(x) / (1 << FIXED_SHIFT))
double logarithm(double base, double num, double result, double weight,int acc)
{
/* Stop recursion once we have reached the desired depth */
if (acc <= 0)
return result;
/* If num is large enough to "remove" one whole unit of log at once */
int32_t num_fixed = TO_FIXED(num);
int32_t base_fixed = TO_FIXED(base);
if (num_fixed >= base_fixed){
int32_t result_fixed = TO_FIXED(result);
int32_t weight_fixed = TO_FIXED(weight);
result_fixed += weight_fixed;
double result_new = TO_DOUBLE(result_fixed);
double weight_new = TO_DOUBLE(weight_fixed);
int64_t tmp_fixed = ((int64_t)num_fixed << FIXED_SHIFT) / base_fixed;
double tmp = TO_DOUBLE(tmp_fixed);
return logarithm(base, tmp, result_new, weight_new, acc - 1);
}
/* Otherwise, multiply 'num' by itself 'base' times
* (which is effectively num^base).
*/
int32_t tmp_fixed = TO_FIXED(1.0);
for (int i = 0; i < (int) base; i++){
tmp_fixed = ((int64_t)tmp_fixed *(int64_t)num_fixed) / (1 << FIXED_SHIFT);
printf("%d\n", (int) base);
printf("%d\n", num_fixed);
printf("%d\n", tmp_fixed);}
double tmp = TO_DOUBLE(tmp_fixed);
int32_t weight_fixed = TO_FIXED(weight);
int64_t weight_scaled = ((int64_t)weight_fixed << FIXED_SHIFT) / base_fixed;
double weight_new = TO_DOUBLE(weight_scaled);
return logarithm(base, tmp, result, weight_new, acc - 1);
}
```
:::danger
應探討誤差和其修正。
:::
### 探討誤差來源
**Q16.16** 格式,保留 16 位小數部分,單層的精度為 $2^{-16} = 0.0000152587890625$ 。
程式碼在迭代的過程, *weight* 不斷除 2 ,最後在 *weight* = 2^{-16} 之後的運算 *weight* 都為 0 ,導致 *result* 沒辦法加到後續的值。
```
int64_t weight_scaled = ((int64_t)weight_fixed << FIXED_SHIFT) / base_fixed;
```
上述程式碼,當 *weight* = 2^{-16} ,也就是 *weight_fixed* = 1 時, *weight_fixed* 再除 2 , *weight_scaled* = 0 。
而且,後續 *weight* 的累積也不會超過 Q16.16 的最小刻度, $2^{-16}$ ,所以這裡不考慮用另外的變數紀錄誤差
那目前計算出來的值 $\log_2(10) \approx 3.321914672852$ 與真實的值 $\log_2(10) \approx 3.3219280948873626$ 相差在 **Q16.16** 的最小刻度內,所以若要更為精準的表示,要用 Q8.24 等能表示更多小數點後位數的格式
### [牛頓法](https://en.wikipedia.org/wiki/Newton%27s_method)
<!-- ```
int32_t LUT[10] = {};
``` -->
<!-- ```c
#include <stdio.h>
#include <stdint.h>
#include <math.h>
#define FIXED_SHIFT 16
#define TO_FIXED(x) ((int32_t)((x) * (1 << FIXED_SHIFT)))
#define TO_DOUBLE(x) ((double)(x) / (1 << FIXED_SHIFT))
double logarithm(double base, double num, double result, double weight,int acc)
{
/* Stop recursion once we have reached the desired depth */
if (acc <= 0)
return result;
/* If num is large enough to "remove" one whole unit of log at once */
int32_t num_fixed = TO_FIXED(num);
int32_t base_fixed = TO_FIXED(base);
if (num_fixed >= base_fixed){
int32_t result_fixed = TO_FIXED(result);
int32_t weight_fixed = TO_FIXED(weight);
result_fixed += weight_fixed;
double result_new = TO_DOUBLE(result_fixed);
double weight_new = TO_DOUBLE(weight_fixed);
int64_t tmp_fixed = ((int64_t)num_fixed << FIXED_SHIFT) / base_fixed;
double tmp = TO_DOUBLE(tmp_fixed);
return logarithm(base, tmp, result_new, weight_new, acc - 1);
}
/* Otherwise, multiply 'num' by itself 'base' times
* (which is effectively num^base).
*/
int64_t f(int64_t x){
int64_t y = pow(2, x) - num_fixed;
return y;}
int64_t dx(int64_t x){
int64_t y = pow(2, x)*TO_FIXED(0.6931471805599453);
return y;}
int64_t x = num_fixed - (1 << FIXED_SHIFT);
for(int i=0;i<100;i++){
x = x - (f(x)/dx(x));}
int32_t result_fixed = TO_FIXED(result);
x = (int64_t)result_fixed + x;
double result_end = TO_DOUBLE(x);
return result_end;
}
int main(void)
{
/* 固定測試參數 */
double base = 2.0; /* 以 2 為底 */
double num = 10.0; /* 要取對數的數值 */
double result = 0.0; /* 初始累加結果 */
double weight = 1.0; /* 當前權重 (1 代表 2^0) */
int acc = 40; /* 遞迴深度,越大越精確 */
double log_value = logarithm(base, num, result, weight, acc);
printf("log_%g(%g) ≈ %.12f\n", base, num, log_value);
return 0;
}
``` -->