---
tags: 作業
---
# 浮點數除法程式
假設 `divop()` 的第二個參數也就是除數,必為大於 `0` 的整數
```cpp=
#include <stdio.h>
#include <stdlib.h>
double divop(double dividend, int divisor) {
if (dividend == 0 || divisor == 1)
return dividend;
int odd = divisor & 1;
double result = divop(dividend/2, odd ? (divisor+1)>>1 : divisor>>1);
if (odd)
result += divop(result, divisor);
return result;
}
```
### 程式說明:
* 第 5 ~ 6 行:
* 判斷被除數為 0 或是除數為 1 的時候回傳被除數,作為遞迴結束的條件
* 除數在第 8 行遞迴會透過 bitwise 右移,所以無論什麼除數最後一定可以得到 1,使遞迴結束
* 這個會使得奇數除數時會有個小循環,不斷 +1 右移直到 1,又再次回到原本的除數 **(第 10 行)**
* 被除數在第 8 行遞迴會直接除以 2,由於型態為 `double`,所以最後除到最小的誤差 `DBL_EPSILON` (定義在 `float.h`),就會視同等於 0 了
* 決定奇數除數時會何時結束,例如改成 `dividend <= 1e-4`,就最多計算到小數點第 5 位
* 第 7 行:
* 判斷當前除數的奇偶,直接比較 LSB 的值即可
* 第 8 行:
* 程式的核心,可以想像成分數,分子分母同 ÷ 2 不影響結果,分母變 1 時分子就是商了
* ÷ 2 對於浮點數來說只是改變 exponent 的部分而已
* 除數為偶數的話比較直觀,兩邊都能同 ÷ 2,尤其是當 2 的指數
* 除數為奇數的話,會先 +1 變成較大的偶數再右移
* 由於多了 +1 的動作,奇數情況的除數並非等同原本的除數,會多除一點使得商相對較小,例如 `1 ÷ 3 = 0.33` 實際上會變成 `1 ÷ 4 = 0.25`
* 除數無論奇偶,過程中依舊可能會得到奇數
* 尤其當除數為質數時,如 `5 -> 3 -> 2`、`13 -> 7 -> 4`、`37 -> 19 -> 10 -> 5`、`101 -> 51 -> 26 -> 13` 等等
* 第 9 ~ 10 行:
* 除數為奇數時,會將遞迴得到的商再次進行遞迴,主要是為了處理 +1 所造成的誤差
* case 1:`1 ÷ 3`
1. 除數會經過 `3 -> 2 -> 1` 兩次,得到 `1 ÷ 4 = 0.25` 的商 **(第 8 行)**
2. 如果把商乘回原本除數就是 `0.25 × 3 = 0.75`,誤差正好等於商 `0.25`
3. 那要多少個 3 可以填補誤差,也就是在計算 `0.25 ÷ 3`,作法同上 **(第 10 行)**
4. 由於 `0.25 ÷ 3` 一定又會有新的誤差,所以當這誤差小於我們設定的條件 **(第 5 行)** 就會結束了
5. 將全部得到的商相加就是最接近的商了 **(第 10 行)**
* case 2:`1 ÷ 5`
1. 除數會經過 `5 -> 3 -> 2 -> 1` 三次,得到 `1 ÷ 8 = 0.125` 的商 **(第 8 行)**
2. 但過程中又經過奇數除數 3 的狀況,也就是當 `0.5 ÷ 3` 時的誤差,`0.125 × 3 = 0.375`,誤差正好是 `0.125`
3. 所以同理又繼續遞迴 `0.125 ÷ 3` **(第 10 行)** 直到終止條件 **(第 5 行)**
4. 到此等同做完一次 ÷ 5,相加得到的商會接近 `0.166...` **(第 10 行)**
5. 把商乘回原本除數 `0.166... × 5 = 0.833...`,誤差再次等於商 `0.166...`
6. 所以同理又繼續遞迴 `0.166... ÷ 5` **(第 10 行)** ,也會再次碰到除數為 3
7. 最終當計算的誤差都小於設定的條件就會結束了
8. 將全部得到的商相加就是最接近的商了 **(第 10 行)**
### 核心概念:
只有相同除數的商才能相加
1. 以 `1 ÷ 5` 為例
2. 當作 `1 ÷ 6` 等價 `0.5 ÷ 3`
3. 當作 `0.5 ÷ 4` 等價 `0.25 ÷ 2`
4. `0.25 ÷ 2` 等價 `0.125 ÷ 1`
(4) => (3) 兩邊同乘 2
(3) => (2) 這些 ÷ 4 的商相加,構成了近似 ÷ 3 的商,也就等價回 ÷ 6
(2) => (1) 這些 ÷ 6 的商相加,構成了近似 ÷ 5 的商,也就是解
> 但實際上逼近的過程是根據較大的 2 的次方數,像 ÷ 5 實際上是用 ÷ 8 逼近,÷ 9 則會是 ÷ 16,因為商都是不斷 ÷ 2 得來的
### 範例:
考慮 `dividend <= 1e-3`
* case 1:`1 ÷ 3`
```cpp=
dividend: 1.0000000000, divisor: 3
dividend: 0.5000000000, divisor: 2
dividend: 0.2500000000, divisor: 1
result: 0.2500000000
dividend: 0.2500000000, divisor: 3
dividend: 0.1250000000, divisor: 2
dividend: 0.0625000000, divisor: 1
result: 0.0625000000
result: 0.0156250000
result: 0.0039062500
result: 0.0009765625
dividend: 0.0009765625, divisor: 3 // <= 1e-4
final: 0.3339843750 // result 總和
```
* case 2:`1 ÷ 5`
```cpp=
dividend: 1.0000000000, divisor: 5 // 對應到 22 行
----------------------------------
dividend: 0.5000000000, divisor: 3
dividend: 0.2500000000, divisor: 2
dividend: 0.1250000000, divisor: 1
result: 0.1250000000
dividend: 0.1250000000, divisor: 3 // 處理 3 情況
dividend: 0.0625000000, divisor: 2
dividend: 0.0312500000, divisor: 1
result: 0.0312500000
dividend: 0.0312500000, divisor: 3
dividend: 0.0156250000, divisor: 2
dividend: 0.0078125000, divisor: 1
result: 0.0078125000
result: 0.0019531250
result: 0.0009765625
result: 0.1679687500 // 第一輪 ÷ 5 的商
----------------------------------
dividend: 0.1679687500, divisor: 5 // 對應到 40 行
----------------------------------
dividend: 0.0839843750, divisor: 3
dividend: 0.0419921875, divisor: 2
dividend: 0.0209960938, divisor: 1
result: 0.0209960938
dividend: 0.0209960938, divisor: 3
dividend: 0.0104980469, divisor: 2
dividend: 0.0052490234, divisor: 1
result: 0.0052490234
result: 0.0013122559
result: 0.0006561279
result: 0.0288696289 // 第二輪誤差的商
----------------------------------
dividend: 0.0288696289, divisor: 5
----------------------------------
dividend: 0.0144348145, divisor: 3
dividend: 0.0072174072, divisor: 2
dividend: 0.0036087036, divisor: 1
result: 0.0036087036
dividend: 0.0036087036, divisor: 3
dividend: 0.0018043518, divisor: 2
dividend: 0.0009021759, divisor: 1
result: 0.0009021759
result: 0.0054130554 // 第三輪誤差的商
----------------------------------
dividend: 0.0054130554, divisor: 5
----------------------------------
dividend: 0.0027065277, divisor: 3
dividend: 0.0013532639, divisor: 2
dividend: 0.0006766319, divisor: 1
result: 0.0006766319
dividend: 0.0006766319, divisor: 3 // <= 1e-4
result: 0.0013532639 // 第四輪誤差的商
----------------------------------
dividend: 0.0013532639, divisor: 5
----------------------------------
dividend: 0.0006766319, divisor: 3 // <= 1e-4
result: 0.0006766319 // 第五輪誤差的商
----------------------------------
dividend: 0.0006766319, divisor: 5 // <= 1e-4
final: 0.2049579620 // result 總和
```