# 2020q3 Homework5 (quiz5) contributed by < `ptzling310` > >[2020q3 第 5 週測驗題](https://hackmd.io/@sysprog/2020-quiz5) ## 測驗 `1`:浮點數除法 ```c= #include <stdio.h> #include <stdlib.h> double divop(double orig, int slots) { if (slots == 1 || orig == 0) return orig; int od = slots & 1; double result = divop(orig / D1, od ? (slots + D2) >> 1 : slots >> 1); if (od) result += divop(result, slots); return result; } ``` - [x] D1 = `2` - [X] D2 = `1` ### 理解 #### 參數 - `double orig`: 被除數 - `int slots`: 除數 #### 程式 ```c=4 if (slots == 1 || orig == 0) return orig; ``` 當以下 case 時,結果即為 `orig`: - $\dfrac{orig}{slots} = \dfrac{orig}{1} = orig$ - $\dfrac{orig}{slots} = \dfrac{0}{1} = 0 =orig$ ```c=7 int od = slots & 1; ``` `od` 用來記錄 `slots` 的 LSB 是否為 bit 1 。 若 `od` == 1 ,表示 `slots` 為奇數。 ```c=8 double result = divop(orig / 2, od ? (slots + 1) >> 1 : slots >> 1); ``` - `od ? (slots + 1) >> 1 : slots >> 1` - `od == 0` 表示 `slots` 為偶數,則運算 $\dfrac{orig}{slots} = \dfrac{\dfrac{orig}{2}}{\dfrac{slots}{2}}$,不會影響 `result` 。 故 D1 = `2`。 - `od == 1` 表示 `slots` 為奇數,則先利用 `+1` 把 `slots` 變成偶數 ($slots'$)再右移。但此舉會造成 `result` 比正確結果來的小 (因為分母變大了)。 ```c=9 if (od) result += divop(result, slots); ``` 在 line 8 時,將 slots 做了 `+1` 的動作,所以回傳的 `result` 並不是真正的答案,這裡就是在進行修正。 舉例: $\dfrac{1}{3}$ (已知答案為 $0.\overline{33}$), 由於 3 不為偶數,故先將 `3+1`, 再求 $\dfrac{1}{4}=\dfrac{0.5}{2}=\dfrac{0.25}{1}=0.25$。(依照 line 8 )。 目前的 result = 0.25。 $result\times slot = 0.25 \times 3 = 0.75 \neq 1$。 故用 `result` 距離 $1$ 還差了 $1-0.75 = 0.25$ ,正好為目前 `result` 的值。 所以只要再補上 $\dfrac{0.25}{3}=0.125$。則 result = 0.25+0.125 = 0.375。 ----------------------------------------- ## 測驗 `2`:浮點數平方根程式 (利用牛頓法找近似值) ### 程式 ```c= #include <stdint.h> /* A union allowing us to convert between a float and a 32-bit integers.*/ typedef union { float value; uint32_t v_int; } ieee_float_shape_type; /* Set a float from a 32 bit int. */ #define INSERT_WORDS(d, ix0) \ do { \ ieee_float_shape_type iw_u; \ iw_u.v_int = (ix0); \ (d) = iw_u.value; \ } while (0) /* Get a 32 bit int from a float. */ #define EXTRACT_WORDS(ix0, d) \ do { \ ieee_float_shape_type ew_u; \ ew_u.value = (d); \ (ix0) = ew_u.v_int; \ } while (0) static const float one = 1.0, tiny = 1.0e-30; float ieee754_sqrt(float x) { float z; int32_t sign = 0x80000000; uint32_t r; int32_t ix0, s0, q, m, t, i; EXTRACT_WORDS(ix0, x); /* take care of zero */ if (ix0 <= 0) { if ((ix0 & (~sign)) == 0) return x; /* sqrt(+-0) = +-0 */ if (ix0 < 0) return (x - x) / (x - x); /* sqrt(-ve) = sNaN */ } /* take care of +INF and NaN */ if ((ix0 & 0x7f800000) == 0x7f800000) { /* sqrt(NaN) = NaN, sqrt(+INF) = +INF,*/ return x; } /* normalize x */ m = (ix0 >> 23); if (m == 0) { /* subnormal x */ for (i = 0; (ix0 & 0x00800000) == 0; i++) ix0 <<= 1; m -= i - 1; } m -= 127; /* unbias exponent */ ix0 = (ix0 & 0x007fffff) | 0x00800000; if (m & 1) { /* odd m, double x to make it even */ ix0 += ix0; } m >>= 1; /* m = [m/2] */ /* generate sqrt(x) bit by bit */ ix0 += ix0; q = s0 = 0; /* [q] = sqrt(x) */ r = QQ0; /* r = moving bit from right to left */ while (r != 0) { t = s0 + r; if (t <= ix0) { s0 = t + r; ix0 -= t; q += r; } ix0 += ix0; r >>= 1; } /* use floating add to find out rounding direction */ if (ix0 != 0) { z = one - tiny; /* trigger inexact flag */ if (z >= one) { z = one + tiny; if (z > one) q += 2; else q += (q & 1); } } ix0 = (q >> 1) + QQ1; ix0 += (m << QQ2); INSERT_WORDS(z, ix0); return z; } ``` ### 理解 #### IEEE 754 表示 float 表示式:$S\times1.Frac\times2^{127+E}$ ```graphviz digraph structs { node[shape=record] NaN[label="float",shape=plaintext]; struct1 [label="S|E|E|E|E|E|E|E|E|F|...|F"]; {rank= NaN; struct1} } ``` :::spoiler 浮點數轉 IEEE格式 過程 6.25 = 110.1 x $2^0$ = 1.101 x $2^2$ 所以 6.25 儲存的格式: Sign(S) = (0)~2~ (因為正數) Exponent(E) = 2 + 127 = 129 = (10000001)~2~ Frac(F) = (1010..0)~2~ ::: #### 概念 將 `float x` 表示為 $x=y\times2^{m}$,當要算 $\sqrt{x}$ 時,就計算$\sqrt{y}\times2^{m/2}$。其中 $2^{m/2}$ 用位移即可達成;而要處理的部分就剩下 $\sqrt{y}$ 的估算。 #### struct ```c=4 typedef union { float value; uint32_t v_int; } ieee_float_shape_type; ``` `value` 是 float 型態,而 `v_int` 是以 `int` 型態保存。 故同一二進制的數值,如果是 value 則代表一個浮點數,如果是 v_int 用來表示整數。 #### function ##### INSERT_WORDS(d, ix0) ```c=9 /* Set a float from a 32 bit int. */ #define INSERT_WORDS(d, ix0) \ do { \ ieee_float_shape_type iw_u; \ iw_u.v_int = (ix0); \ (d) = iw_u.value; \ } while (0) ``` 將 `int` 轉成 `float` 型式 ##### EXTRACT_WORDS(ix0, d) ```c=17 /* Get a 32 bit int from a float. */ #define EXTRACT_WORDS(ix0, d) \ do { \ ieee_float_shape_type ew_u; \ ew_u.value = (d); \ (ix0) = ew_u.v_int; \ } while (0) ``` 將 `float` 轉成 `int` 型式。 ##### ieee754_sqrt(float x) ```c=36 EXTRACT_WORDS(ix0, x); ``` `x` 為 `float` ,藉由 `EXTRACT_WORD` 轉為 `int` 型式的 `ix0`。 ```c=43 /* take care of zero */ if (ix0 <= 0) { if ((ix0 & (~sign)) == 0) return x; /* sqrt(+-0) = +-0 */ if (ix0 < 0) return (x - x) / (x - x); /* sqrt(-ve) = sNaN */ } ``` - `ix0 <= 0` ,也就是 `ix0` 的 MSB (b~31~) = 1:或者 `ix0` = (00..0)~2~。 - `(ix0 & (~sign))==0)` `sign = 0x80000000` = (1000 ... 0000)~2~ , 則 `~sign = 0x80000000` = (0111 ... 1111)~2~。 = `0x7FFFFFFF`。取 `AND` 代表只取 Exponent(E)、Fraction(F) 的部分。 所以這裡條件若成立,表示`ix0` 為 (10...0)~2~ = +0 或 (0...0)~2~ = -0 。 又因 0 開根號還是 0,故 `retrun x`。 - `ix0 < 0`,若成立代表 `ix0` 的 MSB(b~31~) 為 1,表示 `x` 是負數的,但負數不能開根號。 而 `(x - x) / (x - x)` 會為 `0/0` ,歸類在 `Not a Numbe`。 ```c=43 /* take care of +INF and NaN */ if ((ix0 & 0x7f800000) == 0x7f800000) { /* sqrt(NaN) = NaN, sqrt(+INF) = +INF,*/ return x; ``` ```graphviz digraph structs { node[shape=record] NaN[label="NaN",shape=plaintext]; struct1 [label="0/1|11111111|nonZero"]; {rank= NaN; struct1} INF[label="INF",shape=plaintext]; struct2 [label="0/1 | 11111111| 0..0"]; {rank= INF; struct2} } ``` 若 Exponent(E) 的部分皆為 1 ,則為 NaN 或 INF 的情況,故 `retrun x`及可。 ```c=48 /* normalize x */ m = (ix0 >> 23); if (m == 0) { /* subnormal = denormal x */ for (i = 0; (ix0 & 0x00800000) == 0; i++) ix0 <<= 1; m -= i - 1; } ``` `m` 是保留 `ix0` 的 Sign(S) 、 Exponent(E)。 加上在前面已經過濾掉 MSB(b~31~) = 1 的 `ix0`,這裡如果要符合 denormal 的條件的話,就會為: ```graphviz digraph structs { node[shape=record] NaN[label="denormal",shape=plaintext]; struct1 [label="0|00000000|nonZero"]; {rank= NaN; struct1} } ``` - 若 `m = 0` 則符合 denormal 的條件 (S、E 皆 0 )。 若為 denormal,則 `float x` = $0.Fraciton\times2^{-126}$。 - `& 0x00800000` 就是在取第9個 bit (b~23~),正為 Exponent 的最後一個 bit。 故 `ix0 & 0x00800000` 的目的是為了讓 Exponent 的位置產生 1。但這樣的移動會使得這個值變得不正確。 :::info 舉例: | 0 | 00000000 | 0110...0 | | -------- | -------- | -------- | 此為一 denormal float point,值為:$(2^{-2}+2^{-3})\times2^{-126}=2^{-128}+2^{-129}$ 經由 line `50`~`52`,會將 Fraction 的最左 1 ,轉為以下: | 0 | 00000001 | 1000...0 | | -------- | -------- | -------- | 此為一 normal float point,值為:$(1+2^{-1})\times2^{-126}=2^{-126}\times2^{-127}$。 所以必定要進行修正,將值修為正確的值。 修正後值的計算:$(1+2^{-1})\times2^{1-127-3+1}=(1+2^{-1})\times2^{-128}=2^{-128}+2^{-129}$ ※ 因為 for 的 `i++` ,所以會記錄到 `i=3`。 ::: - denormal float point 仍可開根號,為了讓這類型的數字可以一同套用同一個估算開根號的程式,先將該值轉為 normal 的形式 (就是 exponent 非全0)。 ```c=55 m -= 127; /* unbias exponent */ ``` 由於儲存為 binary 的 expoenet 的部分是經由 `+127` 後所得,為得到表示式中的二的次方之值,將 `m - 127` 還原。此舉可以取的浮點數真正的指數值。 ```c=56 ix0 = (ix0 & 0x007fffff) | 0x00800000; ``` 取 `ix0` 的 Frac 以及將 **Exponent(E) 的** LSB 設 1。 如此可得$1.fraction$ ```c=57 if (m & 1) { /* odd m, double x to make it even */ ix0 += ix0; } m >>= 1; /* m = [m/2] */ ``` `m` 有以下兩種情況: - $m$ 為偶數: $x = y\times2^{m},\sqrt{x}=\sqrt{y\times2^{m}}=\sqrt{y}\times2^{m/2}$ - $m$ 為奇數:$x=y\times 2^{m} , \sqrt{x} = \sqrt{y\times2\times2^{m-1}}=\sqrt{2y\times2^{m-1}}=\sqrt{2y}\times2^{m-1/2}$ ```c=62 /* generate sqrt(x) bit by bit */ ix0 += ix0; q = s0 = 0; /* [q] = sqrt(x) */ r = QQ0; /* r = moving bit from right to left */ while (r != 0) { t = s0 + r; if (t <= ix0) { s0 = t + r; ix0 -= t; q += r; } ix0 += ix0; r >>= 1; } ``` > 參考 <[RinHizakura](https://hackmd.io/@RinHizakura/SJnXiSewD#%E6%B8%AC%E9%A9%97-2)>、<[zzzxxx00019](https://hackmd.io/@jT29vQ4-SxmlBU5UMj1TGA/quiz5#%E6%B8%AC%E9%A9%97-2)> 此段程式就是在處理 $\sqrt{y}$ 的估算。 假設:$1\leq y<4 \Rightarrow 1 \leq \sqrt{y}<2$ :::info 為何 $y$ 的範圍介於 $[ 1 , 4 )$? 這裡目標是將 $\sqrt{x} = \sqrt{y} \times 2^{m/2}$ 求出, 目前已在 line `55` 找出 $m$ , line `56` 將 `ix0` = $y$ 找出。 又 $y$ 為 $1.fracion$ ,必定在區間 $[ 1 , 2)$, 在 line `58`,若 $m$ 為奇數,則會提出一個 2 乘入, 所以 $y$ 的範圍會改為: $[ 1 , 4 )$。 ::: :::spoiler 如何逼近 假設現在要從 (1)~2~ 逼近 (1.011)~2~, - (1.1)~2~ > (1.011)~2~,故小數點後一位 bit 應設 0 。 - (1.01)~2~ < (1.011)~2~,故小數點後兩位 bit 應設 1 。 - (1.011)~2~= (1.011)~2~,已相等,完成。 :::info - $q_i$:逼近 $\sqrt{y}$ 至小數點後 $i$ 位。$q_0 = 1$ ,我們從 1 開始逼近。 - $(q_i+2^{-(i+1)})^{2}$ 來逼近 $y$。目前已經判斷到小數點後第 $i$ 位 ($q_i$),則就在往後判斷一位 ($2^{-(i+1)}$) 要為 0 或 1。 - 規則:從上點 "如何逼近" 便可理解,要做的事情就是判斷該位子該填 bit 0 或 bit 1。 - $(q_i+2^{-(i+1)})^{2} \leq y$,代表小數點後第 `i+1`個位子要補上 bit 1,則 $q_{i+1} = q_i +2^{-(i+1)}$。 - $(q_i+2^{-(i+1)})^{2} > y$,代表小數點後第 `i+1` 個位子要補上 bit 0,則 $q_{i+1} = q_i$。 - 算式整理 $(q_i+2^{-(i+1)})^{2} \leq y$ 展開上式 $\Rightarrow (q_i)^{2}+2\times q_i \times 2^{-(i+1)}+2^{-2(i+1)} \leq y$ 移項$(q_i)^2$ $\Rightarrow 2\times q_i \times 2^{-(i+1)}+2^{-2(i+1)} \leq y-(q_i)^{2}$ 提出 $2^{-(i+1)}$ $\Rightarrow 2^{-(i+1)}\times(2\times q_i+2^{-(i+1)}) \leq y-(q_i)^2$ 同乘$2^{i+1}$ $\Rightarrow (2\times q_i+2^{-(i+1)}) \leq (y-(q_i)^2)\times 2^{i+1}$ 令 $s_i=2\times q_i, y_i=(y-(q_i)^2)\times 2^{i+1}$ $\Rightarrow s_i+2^{-(i+1)} \leq y_i$ 而汰換成此型式可以利用遞迴的方式求得 $s_i$、$y_i$ - 求 $s_i$、$y_i$ - $s_i+2^{-(i+1)} \leq y_i$ $s_{i+1}=s_i+2^{-i}$ $y_{i+1}=2\times(y_i-s_i-2^{-(i+1)})$ - $s_i+2^{-(i+1)} > y_i$ $s_{i+1}=s_i$ $y_{i+1}=2 \times y_i$ ::: :::spoiler s_i、y_i推導 - $s_i+2^{-(i+1)} \leq y_i$ $\begin{aligned}s_{i+1} &= 2 \times q_{i+1} \\ &=2\times(q_i+2^{-(i+1)}) \\ &=2\times q_i + 2\times 2^{-(i+1)}\\ &=s_i+2^{-i} \end{aligned}$ $\begin{aligned} y_{i+1} &= (y-({q_{i+1})}^{2})\times 2^{(i+1)+1}\\ &=(y-q_i^{2}-2^{-(i+1)}\times s_i-2^{-2(i+1)})\times 2^{i+2}\\ &=(y-q_i^{2})\times2^{i+2}-2^{i+2}\times2^{-(i+1)}\times s_i-2^{i+2}\times2^{-2(i+1)}\\ &=(y-q_i^{2})2^{i+1}\times2-2s_i-2^{-i}\\ &=2y_i-2s_i-2^{-i}\\ &=2\times(y_i-s_i-2^{-(i+1)}) \end{aligned}$ - $s_i+2^{-(i+1)} > y_i$ $\begin{aligned}s_{i+1} &= 2\times q_{i+1}\\ &=2\times q_i\\ &=s_i \end{aligned}$ $\begin{aligned} y_{i+1} &= (y-({q_{i+1})}^{2})\times 2^{(i+1)+1}\\ &=(y-q_i^{2})\times 2^{(i+2)}\\ &=(y-q_i^{2})2^{i+1}\times 2\\ &= 2y_{i} \end{aligned}$ ::: 整理目前所知資訊: - 定義 $s_i=2q_i$,且可使用遞迴求解 $s_{i+1}= \left\{\begin{array}{lr} s_i+2^{-i}, s_i+2^{-(i+1)} \leq y_i\\ s_i , s_i+2^{-(i+1)} > y_i\\ \end{array} \right.$ - 定義 $y_i=(y-(q_i)^2)\times 2^{i+1}$,且可使用遞迴求解 $y_{i+1}= \left\{\begin{array}{lr} 2\times(y_i-s_i-2^{-(i+1)}), s_i+2^{-(i+1)} \leq y_i\\ 2\times y_i , s_i+2^{-(i+1)} > y_i\\ \end{array} \right.$ Line `63` :在 line `78` 開始有進行進位判斷,所以這裡將 `ix0` 左移一位,以使小數部分可以多一位來進行進位(現在小數部分有 24 bits了)。 Line `65` : `r` 的功能是用來一個 bit 一個 bit 的檢查,首先小數部分要先保留 24 bits (b~0~-b~23~),第 25 bit (b~24~)才為整數之部分,所以從第 25 bit (b~24~) 開始檢查,故 QQ0 = `0x01000000` ,從 $2^0 = 1$ 開始逼近。 Line `68` : `t = s0 + r`。即:`t` = $s_i+2^{-(i+1)}$。 從 line `69`~`72` 處理 $= s_i+2^{-(i+1)} \leq y_i$ 之情況。 Line `69` : `if (t <= ix0)`。即:若`t` $= s_i+2^{-(i+1)} \leq y_i$ Line `70` : `s0 = t + r;`。即: $s_{i+1}=s_i+2^{-(i+1)}+2^{-(i+1)}=s_i+2^{-i}$ Line `71` : `ix0 -= t;`。即: $y_{i+1}=y_i-(s_i+2^{-(i+1)})=y_i-s_i-2^{-(i+1)}$ Line `72` : `q += r;`。即:$q_{i+1}=q_i+2^{-(i+1)}$ Line `74` : `ix0 += ix0;`。即 $y_i=2\times2y_i$。在$y_{i+1}$定義中,不論條件為何,皆有一個$\times 2$這個動作,就在此處完成。如此對$y_{i+1}$的處理才算完整。 Line `75` : `r >>= 1;`。即查看下一個 bit (從$2^{-(i+1)}$ 變 $2^{-(i+2)}$)。 ```c=78 /* use floating add to find out rounding direction */ if (ix0 != 0) { z = one - tiny; /* trigger inexact flag */ if (z >= one) { z = one + tiny; if (z > one) q += 2; else q += (q & 1); } } ``` Line `80` :照理來說 `z` 是一個比 1 還略小的數字(0.9....99)。在 line `81` 會判斷說這個 `z` 是否有小於 1,若無則在表這個系統有進行進位。 Line `82` : 照理來說 `z` 是一個比 1 還略大的數字(1.0...01)。在 line `83` 會判斷說這個 `z` 是否有大於 1,若有則表示此系統採無條件進位。 目前第 1 bit (b~0~) 是用來判斷進位用的,由於這裡是無條件進位,藉由 `+2` = +(10)~2~ 將 1 加到 b~1~。 Line `86` :此處表示 `z` = 1,也就是此系統是採用四捨五入,唯有第 1 個 bit(b~0~) = 1 才會進位。 ```c=90 ix0 = (q >> 1) + QQ1; ix0 += (m << QQ2); INSERT_WORDS(z, ix0); ``` - `(q >> 1)` 的目的是要將第 1 個 bit(b~0~) 消去,因為這個 bit 是多出來做進位判斷用的。 - 由於浮點數的 Exponent(E) 的必須 `+127`,但在前面我們已將 Exponent(E) 取 1,所以這裡在補上 126,故 QQ1 = `0x3F000000`。 - 將 `m` 補回正確位置,故 QQ2 = `23`。 - 最後在 line `93` 轉回 `float` 型式。 ------------------ ## 測驗`3`: [LeetCode 829. Consecutive Numbers Sum ](https://leetcode.com/problems/consecutive-numbers-sum/) ### 程式 ```c= int consecutiveNumbersSum(int N) { if (N < 1) return 0; int ret = 1; int x = N; for (int i = 2; i < x; i++) { x += ZZZ; if (x % i == 0) ret++; } return ret; } ``` - [x] ZZZ=`1-i` ### 理解 ```c=5 int ret = 1; ``` `ret` 用來記錄組合數。 ```c=7 for (int i = 2; i < x; i++) { x += ZZZ; ``` $\begin{aligned} 令N &= x + (x+1) + ... + [x+(k-1)] \\ &= kx\times(1+2+...+(k-1)) \\ &= kx + \dfrac{k\times(k-1)}{2} \\ 整理後得: \\ kx &= N - \dfrac{k\times(k-1)}{2} …(1) \end{aligned}$ 由於$k$是給定任意值,所以是會對應到變數`i`。 而在 line `6` 中將 `N` 指派給 `x`,所以 `x` 應用來紀錄 $N-\dfrac{k\times(k-1)}{2}$。 `x += ZZZ;` 就是在處理 $-\dfrac{k\times(k-1)}{2}$。 $\begin{aligned} -\dfrac{k\times(k-1)}{2} &= - [1+2+...+(k-1)]\\ &= [-1-2-...-(k-1)]\\ \end{aligned}$ 每次都在處理 $-(k-1)=-k+1=1-k$, 又這裡的 $k$ 就是 `i`,所以 ZZZ = `1-i`。 - 為何不從 i = 1 開始? 由於在 line `5`:`ret=1`,若這裡 `i=1` ,則 $x=N$,會在將 `ret` 又多算一次。 ```c=9 if (x % i == 0) ret++; ``` 回顧 $算式 (1)$,如果可得一$x$為整數解,則代表可以對應到一個整數組合。 $kx = N - \dfrac{k\times(k-1)}{2}\Rightarrow x= \dfrac{N - \dfrac{k\times(k-1)}{2}}{k} 為整數$。 所以只要確認 $x$ 是否為 $k$ 之倍數就可判斷這組整數組合是否存在。 ###### tags: `sysprog2020`