---
# System prepended metadata

title: 2024q1 Homework5 (assessment)

---

# 2024q1 Homework5 (assessment)
contributed by < `marsh-fish` >

## 閱讀〈[因為自動飲料機而延畢的那一年](https://blog.opasschang.com/the-story-of-auto-beverage-machine-1)〉的啟發

我非常喜歡作者引用了《鋼之鍊金術師》的金句，許多老一輩的人都認為看動漫是件幼稚的行為，但動漫裡頭能讓人學習到的東西非常的多(當然會因作品而異)，不應該因自身的「刻板印象」對他人的喜好亂下評斷，但人又何嘗不是如此呢?回到文章，作者在專案前期預期著完美的理想成品，但由許多的理想都難以實現，光是自動掉冰塊機就花了相當多的時間才有成品，我一看到作者初期對自動掉冰塊機的想像，就有相當大的疑問，當下一直在思考「如果是我做的話會怎麼做?」，看到作者想用彈簧實現，就讓我想到當初檳榔去鬚機的出代版本也是用彈簧，後來才改成現在常見的形式，不過後來作者是直接購買冰塊分配器就是了。「三折肱為良醫」究竟是折肱三次方能成為良醫，還是幫助過三個「折肱」人即成為良醫呢?當然，字典會告訴你答案是前者，但若折肱這件事能為你帶來啟發，何必是「自己」來折肱呢?《因為自動飲料機而延畢的那一年》最後的結局雖不是最完美，但就如《灌籃高手》、《棋靈王》這樣的故事，往往能帶給人來更多的感觸，我也不禁感動落淚。

## 想投入的專案

### kHTTPd

### 改進 Linux 核心的 lib/{list_}sort.c

```c
int mul_10(int x)
{
    // return x * 10
    return (x << 3) + x + x;
}
```

```c
float float_mul10(float x)
{
    // Using bitwise operation; No mul/div
    unsigned sign =  (unsigned)x >> 31;
    unsigned exponent =  ((unsigned)x << 1) >> 23;
    unsigned frac = ((unsigned)x << 9) >> 9;
    
    unsigned frac_re;
    frac_re = frac << 3 + frac + frac;
    
    unsigned re = (sign << 31) | (exponent << 23) | (frac_re);
    return (float)re;
}
```
> https://numeral-systems.com/ieee-754-converter/

4.0 => 01000000100000000000000000000000
4.9 => 01000000100111001100110011001101

classified as
- power of 2: copy * 2; copy * 8
- not power2

TODO: 撰寫程式碼、測試，附上分析

TODO: 研究紅黑樹: https://hackmd.io/@sysprog/rJbMiW3S3 紀錄問題並重現實驗 + extra

---
## TODO: 撰寫程式碼、測試，附上分析

將 `sign`, `exponent`, `frac` 的值分別以二進制印出，會發現最初程式碼的型別轉換並不能達到我所期望的結果(轉成 IEEE 754 的格式)。
```bash 
    x = 4.0
    sign = 0
    exponent = 0
    frac = 100
```
經過嘗試發現可以藉由 unsigned 指標強制轉成 IEEE 754 的格式
```bash 
    unsigned* p = (unsigned*)&x;
    x = 4.0
    p = 0100 0000 1000 0000 0000 0000 0000 0000
    sign = 0
    exponent = 1000 0001
    frac = 000 0000 0000 0000 0000 0000
```
將三個區塊個別取出
```c 
    unsigned sign = *(unsigned*)&x >> 31;
    unsigned exponent = (*(unsigned*)&x << 1) >> 24;
    unsigned frac = (*(unsigned*)&x << 9) >> 9;
    sign = 0
    exponent = 1000 0001
    frac = 000 0000 0000 0000 0000 0000
```
可以將乘十分解成乘八加乘二，這裡必須注意的是在 IEEE 754 浮點數的格式中分數 (fraction) 部分最高有效位 (即整數位) 是 1 ，因此在做加法運算的時候需將 1 補上，如下
```c 
    frac = (frac << 3) + ((frac | 0x00800000) << 1);
```
接下來會有兩種情況，進位與無進位，因為兩者要做的位移數不同因此用 `if` 判斷是否進位，無進位的情況比較簡單，只要補齊指數部分和位移分數部分。進位的情況下，進位的發生是由於我們在先前多加了一個整數一，所以這個整數一必須把他移除，所以對第 23 個 bit 做 AND 運算。
```c
    if(frac >> 26){
        exponent = exponent + 4;
        frac = (frac >> 4) & 0xffbfffff;
    }
    else{
        exponent = exponent + 3;
        frac = (frac >> 3);
    }
```
最後將 IEEE 754 的格式用與第一步反向的方式轉回浮點數便會得到乘十的結果
```c
    float re;
    unsigned *pre = (unsigned*)&re;
    *pre = ((sign << 31) | (exponent << 23) | (frac));
    return re;
```
實驗看看各式各樣的值是否正確
```bash 
x:4.9
49.000000
49.000000

x:3.9
39.000000
39.000000

x:6.4
64.000000
64.000000

x:127.9
1279.000000
1279.000000
```
~~在進一步探討會發現使用 `x*10` 得出的結果會較貼近 `x` 在程式裡的近似值再去乘十，而用我的函式在大多數的情況會得到較貼近實數乘十的結果，例如 x = 13.4 的時候，印出 x 會得到 13.400015 ，而 `x*10` 會得到 134.000153 ，我的函式會得到 134.000137，底下列出部分結果。~~ 後來發現此實驗方式有誤，但我認為可以記錄一下，往後在做浮點數的要避免使用浮點數的加法，避開不可預期之事。

在進一步探討會發現在僅有小數第一位有值的大多數情況，我的函式的結果會略少於實數乘十的結果
```bash
x:2.300000
23.000000
22.999998

x:2.600000
26.000000
25.999998

x:2.800000
28.000000
27.999998

x:3.100000
31.000000
30.999998

x:23.400000
234.000000
233.999985
```
也有發現在小數位數較多的情形下，我的函數可能會較貼近實數乘十的結果(應當只有少數的 x 會成立)
```bash
x:3.14159265
31.415928
31.415926

x:2.589847
25.898472
25.898470
```

