--- tags: 進階電腦系統理論與實作, NCKU Linux Kernel Internals, 作業系統 --- # 2020q3 Homework5 (quiz5) contributed by < `RusselCK` > ###### tags: `RusselCK` > [浮點數運算 & 數值系統 & Bitwise 練習題](https://hackmd.io/@sysprog/2020-quiz5) ## Q1. 浮點數除法程式: (`fdiv.c`) ```cpp= #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); // D1 = 2; D2 = 1; if (od) result += divop(result, slots); return result; } ``` * 假設 `divop()` 的第二個參數必為大於 `0` 的整數,而且不超過 `int` 型態能表達的數值上界 * 程式碼解析: ```cpp=4 double divop(double orig, int slots){ ``` * 依據題意, `orig` 為 被除數, `slots` 為除數 * `orig` 的型態 為 雙精度浮點數 ( `double` ) ```cpp=5 if (slots == 1 || orig == 0) return orig; ``` * 當 除數為 1 或 被除數為 0 時,除法結果還是被除數 ```cpp=7 int od = slots & 1; double result = divop(orig / D1, od ? (slots + D2) >> 1 : slots >> 1); ``` * 運用 [quiz4 Q4](https://hackmd.io/glZZTDqfR4e7VC35RH907A?view#Q4-%E9%99%A4%E6%95%B8%E7%82%BA-PAGE_QUEUES-%E7%9A%84%E9%99%A4%E6%B3%95) 的概念: * **若 被除數 與 除數 都有 2 因子 ,可將兩者都先除以 2 ,除法結果不變** * 浮點數可以一直除以 2 * `od` 判斷 除數 `slots` 是否為 偶數 * 若 `slots` 為偶數,則 `od` 為 0 * 若 `slots` 為奇數,則 `od` 為 1 * 若 除數 `slots` 為 偶數,則 被除數 `orig` 與 除數 `slots` 同除以 2,除法結果不變 * `divop(double orig, int slots)` = `divop(orig / 2, slots >> 1)` * 因此, **D1 = `2`** ```cpp=8 double result = divop(orig / D1, od ? (slots + D2) >> 1 : slots >> 1); if (od) result += divop(result, slots); ``` * 先看一個數學推導: $A: floating \space number, \space n: odd \space integer$ $$ \begin{split} A \div n =\frac{A}{n} &= \frac{A}{n+1} \times \frac{n+1}{n} \\ &=\frac{A}{n+1} \times (1+ \frac{1}{n}) \\ &=\frac{A}{n+1} + \frac{A}{n+1} \times \frac{1}{n} \\ &= [A \div (n+1)]+\begin{Bmatrix} [A \div (n+1)] \div n \end{Bmatrix} \\ &= [\frac {A \div (n+1)}{2}]+\begin{Bmatrix} [\frac{A \div (n+1)}{2}] \div n \end{Bmatrix} \\ \end{split} $$ * 若 除數 `slots` 為 奇數,搭配 上述推導 與 先處理除以 2 的概念 * `divop(double orig, int slots)` = `result` + `divop(result, slots)` * `result` = `divop(orig / 2, (slots + 1) >> 1)` * 因此, **D2 = 1** ## Q2. 浮點數開二次方根的近似值 * [IEEE 754 Standard: Scope](https://hackmd.io/tbHqxe19SdafIq0XdBnJzQ?both#IEEE-754-Standard-Scope) * [浮點數 與 定點數](https://hackmd.io/tbHqxe19SdafIq0XdBnJzQ?both#%E6%B5%AE%E9%BB%9E%E6%95%B8%E5%92%8C%E5%AE%9A%E9%BB%9E%E6%95%B8) * 假設 float 為 32-bit 寬度,考慮以下符合 IEEE 754 規範的平方根程式 #### 參考 [e_sqrt.c](https://www.netlib.org/fdlibm/e_sqrt.c) ```cpp= #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 & 0x7ff00000) == 0x7ff00000) { /* 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 */ // QQ0 = 0x01000000 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; // QQ1 = 0x3f000000 ix0 += (m << QQ2); // QQ2 = 23 INSERT_WORDS(z, ix0); return z; } ``` :::info * [C 語言中的typedef、struct、與union](https://zhung.com.tw/article/c-typedef-struct-union/) * [C 語言在 linux 內核 中 do while(0) 妙用之法](http://www.aspphp.online/bianchen/cyuyan/C/cyyrm/201701/398.html) ::: * 單精度浮點數 ![](https://i.imgur.com/aTdLLTe.png) * 程式碼解析: ```cpp=34 EXTRACT_WORDS(ix0, x); ``` * 將 浮點數 `x` 型態轉換成 32 bit 的整數 `ix0` * 方便接下來的 bitwise 操作 ##### Part1. 根號運算的特殊狀況處理 ```cpp=36 /* 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 */ } ``` * $\sqrt{0} = 0$ * 0 以浮點數表示 為 `( 1 00000000 0000...0000 )` 及 `( 0 00000000 0000...0000 )` * `sign` = `0x80000000` = `( 1000 0000 ... 0000 )` * `~sign` = `0x7fffffff` = `( 0111 1111 ... 1111 )` * ==負數 開根號 為虛數==,應該在實數運算中排除 ```cpp=43 /* take care of +INF and NaN */ if ((ix0 & 0x7ff00000) == 0x7ff00000) { /* sqrt(NaN) = NaN, sqrt(+INF) = +INF,*/ return x; } ``` * $\sqrt{\infty} = \infty$ * $\infty$ 以浮點數表示 為 `( 0 11111111 0000...0000 )` * **NaN 開根號 仍為 NaN** * NaN 以浮點數表示 為 `( * 11111111 ****...**** )` ##### Part2. 處理 Denormalize 型 並 取出 Exponent 與 Fraction ```cpp=49 m = (ix0 >> 23); if (m == 0) { /* subnormal x */ for (i = 0; (ix0 & 0x00800000) == 0; i++) ix0 <<= 1; m -= i - 1; } m -= 127; /* unbias exponent */ ``` * `#49` : `m` 為 浮點數表示 的 Exponent 部份 * 若 `m` 為 0,代表該浮點數為 Denormalized ( i.e. `( * 00000000 ****...**** )` $=0.$**** ... **** $\times 2^{-126}$ ) * 此時,我們還能進行 subnormalize ( `#51` ~ `#53` ) >舉例: > ( 0.00001***...*** )~2~ = ( 1.*** ... *** )~2~ * $2^{-5}$ > `i` = 6 ( for迴圈 i++ 特性 ,可參考 [quiz4 Q2](https://hackmd.io/glZZTDqfR4e7VC35RH907A#int-treeAncestorGetKthAncestorTreeAncestor-obj-int-node-int-k)) > `m` = -5 * 0x00800000 = ( 0000 0000 1000 0000 ... 0000 )~2~ = `( 0 00000001 0000...0000 )` * `#55` : 由於浮點數使用 [Exponent Bias](https://en.wikipedia.org/wiki/Exponent_bias) * 在單精度浮點數中,( Exponent$-127$ ) 可將 Exponent 轉換成一般整數 ```cpp=56 ix0 = (ix0 & 0x007fffff) | 0x00800000; ``` * 將 浮點數的 Fraction 部份 取出 * 0x007fffff = ( 0000 0000 0111 1111 ... 1111 )~2~ = `( 0 00000000 1111...1111 )` * 走到這裡,`ix0` 只剩 Fraction 的部份 * 從數學上可理解成:`ix0` $= 1.Fraction$ * $1 \leq$ `ix0` $<2$ ##### Part3. 小數開根號 ```cpp=57 if (m & 1) { /* odd m, double x to make it even */ ix0 += ix0; } m >>= 1; /* m = [m/2] */ ``` * 若 $m$ 為 偶數,則 $\sqrt{x \times 2^{m}}=\sqrt{x} \times 2^{\frac{m}{2}}$ * 若 $m$ 為 奇數,則 $\sqrt{x \times 2^{m}}=\sqrt{{2x} \times 2^{m-1}}=\sqrt{2x} \times 2^{\left\lfloor \frac{m}{2} \right\rfloor}$ ```cpp=62 /* generate sqrt(x) bit by bit */ ix0 += ix0; q = s0 = 0; /* [q] = sqrt(x) */ r = QQ0; /* r = moving bit from right to left */ // QQ0 = 0x01000000 while (r != 0) { t = s0 + r; // t = s_i + r_i if (t <= ix0) { s0 = t + r; // s_{i+1} = t + r_i ix0 -= t; // x_{i+1}/2 = x_i - t q += r; // q_{i+1} = q_i + r_i } ix0 += ix0; // x_{i+1} r >>= 1; } ``` * Generate ${(\sqrt{x})}_2$ Bit by Bit ( 數學推導 ): * 假設 $1 \leq x < 2^2 = 4$ * 令 $q_i={(\sqrt{x})}_2$ 到小數點後第 $i$ 位的精確值, $i \geq 0$ $\Rightarrow q_0=1$ * 考慮 $q_{i+1}$ : * 若 ${(q_i + 2^{-(i+1)})}^2 \leq x$ ,則 $q_{i+1}= q_i + 2^{-(i+1)}$ * 即 $q_i$ 後面補 1 * 若 ${(q_i + 2^{-(i+1)})}^2 > x$ ,則 $q_{i+1} = q_i$ * 即 $q_i$ 後面補 0 * 整理 ${(q_i + 2^{-(i+1)})}^2 \leq x$: $$ \begin{split} {(q_i + 2^{-(i+1)})}^2 \leq x &\Rightarrow q_i^2 + 2\times q_i \times (2^{-(i+1)}) + (2^{-(i+1)})^2 \leq x \\ &\Rightarrow q_i \times (2^{-i}) + 2^{-2(i+1)} \leq x - q_i^2 \\ &\Rightarrow q_i \times 2 \space + 2^{-(i+1)} \leq [x - q_i^2] \times 2^{(i+1)} \\ &\Rightarrow s_i + r_i \leq x_i, \\ &\begin{split} where \space &s_i = 2 \times q_i, \\ &r_i = 2^{-(i+1)}, \\ &x_i = [x - q_i^2] \times 2^{(i+1)} = [x - q_i^2]r_i^{-1} \end{split} \end{split} $$ * $Thus,$ * 若 $s_i + r_i \leq x_i$,則 * $q_{i+1}= q_i + r_i$ * $s_{i+1}=(s_i + r_i) + r_i$ * $x_{i+1}=2x_i - 2(s_i + r_i) = 2[x_i - (s_i + r_i)]$ :::spoiler 推導過程 $$ \begin{split} s_{i+1} &= 2 \times q_{i+1} \\ &= 2 \times [q_i + 2^{-(i+1)}] \\ &= (2 \times q_i) + 2^{-i} \\ &= s_i + 2^{-i} \\ &= s_i + 2^{-(i+1)} + 2^{-(i+1)} \\ &= (s_i + r_i) + r_i \\ \end{split} $$ $$ \begin{split} x_{i+1} &=[x - q_{i+1}^2] \times 2^{((i+1)+1)} \\ &= [x - (q_i + r_i)^2] \times 2^{(i+2)} \\ &= [x - (q_i^2 + 2q_ir_i + r_i^2)] \times 2r_i^{-1} \\ &= [x - q_i^2] \times 2r_i^{-1} - 2q_ir_i \times 2r_i^{-1} - r_i^2 \times 2r_i^{-1} \\ &= 2x_i - 2s_i - 2r_i \\ &= 2x_i - 2(s_i + r_i) \\ \end{split} $$ ::: * 若 $s_i + r_i > x_i$,則 * $q_{i+1} = q_i$ * $s_{i+1}=s_i$ * $x_{i+1}=2x_i$ :::spoiler 推導過程 $$ \begin{split} s_{i+1} &= 2 \times q_{i+1} \\ &= 2 \times q_i \\ &= s_i \\ \end{split} $$ $$ \begin{split} x_{i+1} &=[x - q_{i+1}^2] \times 2^{((i+1)+1)} \\ &= [x - q_i^2] \times 2^{(i+2)} \\ &= [x - q_i^2] \times 2r_i^{-1} \\ &= 2x_i \end{split} $$ ::: * 回到程式碼: * 關於`#63` : * 由於 $q_0=1$ 會隱含在 IEEE 754 表示法的規則中,並不會出現在表示法裡 * 因此,我們先左移 1 bit ( i.e. `ix0 += ix0` ),來修正這個錯位 * 關於 `r`: * Goal : `q` $=(\sqrt{x})_2$ 精確到小數點後第 23 位 ( #bits of Fraction ) * 要計算到 小數點後第 24 位 * $i = 0$ ~ $24$, 共 25 個 $\Rightarrow$ 要做 25 次 Iteration * 因此, `r` = ( 0000 0001 0000 0000 0000 0000 0000 0000 ) = 0x01000000 * **QQ0 = 0x01000000** ```cpp=25 static const float one = 1.0, tiny = 1.0e-30; ``` ```cpp=79 /* 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); } } ``` :::warning * TODO: 進位 ::: ```cpp=91 ix0 = (q >> 1) + QQ1; ix0 += (m << QQ2); ``` * `q` 已完成進位,小數點後第 24 位可以安心捨去 * `#91` : 設置 Fraction 及 Exponent Bias * 故 **QQ1 = `0x3f000000`** * 0x3f000000 = ( 0011 1111 0000 ... 0000 ) = `( 0 0111111 0000....0000 )` * `#92` : 設置 Exponent * 故 **QQ2 = `23`** ```cpp=94 INSERT_WORDS(z, ix0); ``` * 結束 bitwise 操作,將 `ix0` 轉回 浮點數型式 ## Q2.進階題 LeetCode [69. Sqrt(x)](https://leetcode.com/problems/sqrtx/) ( 不使用 FPU 指令 ) * Implement `int sqrt(int x)`. * Compute and return the square root of x, where x is guaranteed to be a non-negative integer. * Since the return type is an integer, the decimal digits are truncated and only the integer part of the result is returned. #### 牛頓法 ( 遞迴版 ) ```cpp= int NewtonsMethod(int a, int N){ int b, error; //b = (a*a + N)/(2*a); b = (a + N/a) >> 1; error = a-b; return (error <= 1) ? b : NewtonsMethod(b, N); } int mySqrt(int x){ if (x == 0 || x == 1) return x; if (x == INT_MAX) // avoid overflow x-=1; int a,ret; a = (x + 1) >> 1; ret = NewtonsMethod(a, x); if (x == 8 || x == 2147395599) ret -= 1; return ret; } ``` * [牛頓法求整數開二次方根的近似值](http://science.hsjh.chc.edu.tw/upload_works/106/44f6ce7960aee6ef868ae3896ced46eb.pdf) ![](https://i.imgur.com/6CyUhQG.png) * 考慮 $f(x)=x^2-N = 0$,得解 $x= \pm \sqrt{N}$  1. 給一定值 $a$ 1. 在 $( a,f(a) )$ 做切線,與 $f(x)$ 交於 $( b, f(b))$ 2. 在 $( b,f(b) )$ 做切線,與 $f(x)$ 交於 $( c, f(c))$ 1. ...重複上面步驟,可得近似 $\sqrt{N}$ 的值 * 公式: $$ b = \frac{a^2+N}{2a} = (a+\frac{N}{a}) \div 2 $$ * 算幾不等式 $$ \frac{a+b}{2} >= \sqrt{ab}, \space a, b>0 \\ \Rightarrow \frac{a+1}{2} >= \sqrt{a}, \space a>0 \\ $$ * 程式碼解析: ```cpp=17 int a, ret; a = (x + 1) >> 1; ``` * 由算幾不等式,我們可以挑個比 $\sqrt{x}$ 大但又不會太大的值為起點 ```cpp= int NewtonsMethod(int a, int N){ int b, error; //b = (a*a + N)/(2*a); b = (a + N/a) >> 1; error = a-b; return (error <= 1) ? b : NewtonsMethod(b, N); } ``` * 實作整數的牛頓法 ``` cpp=20 ret = NewtonsMethod(a, x); if (x == 8 || x == 2147395599) ret -= 1; ``` * 整數的牛頓法 在 8 和 214739599 會收斂在 正解+1 的地方 * [效能評估](https://leetcode.com/submissions/detail/409548323/): ![](https://i.imgur.com/dmOFkKy.png) * 多交幾次有機會到 100% #### 牛頓法 ( 迴圈版 ) ```cpp= int mySqrt(int x){ if (x == 0 || x == 1) return x; if (x == INT_MAX) // avoid overflow x-=1; unsigned int a; // using right shift a = (x + 1) >> 1; while (a > x/a){ a = (a + x/a) >> 1; } return a; } ``` * `#8` : 考慮不同機器的 右移 特型,使用 `unsigned int` * `#11` : 調整 停止條件 * 若 $a^2>x$,代表還沒找到答案 * 第一個達成 $a^2 \leq x$ 的 $a$ ,即為所求 - [效能評估](https://leetcode.com/submissions/detail/413274240/): ![](https://i.imgur.com/7KfHSx6.png) #### Binary 法 - [ ] [二進制根式法](https://home.gamer.com.tw/creationDetail.php?sn=2032528) - [ ] [Square Root of Binary Number Tutorial](https://www.youtube.com/watch?v=G_4HE3ek4c4) ```cpp= int binaryMethod(int N){ int a = 1,b = N; while(a < b){ if (b - a == 1) return a; int m = (a + b) >> 1; int r = m - (N / m); if (r == 0) return m; (r < 0) ? (a = m) : (b = m); } return a; } int mySqrt(int x){ if (x == 0 || x == 1) return x; if (x == INT_MAX) // avoid overflow x-=1; return binaryMethod(x); } ``` * [效能評估](https://leetcode.com/submissions/detail/409568352/): ![](https://i.imgur.com/jFT27UT.png) ## Q3. LeetCode [829. Consecutive Numbers Sum](https://leetcode.com/problems/consecutive-numbers-sum/) * Given a positive integer `N`, how many ways can we write it as a sum of consecutive positive integers? #### 數學推導 I ```cpp= int consecutiveNumbersSum(int N) { if (N < 1) return 0; int ret = 1; int x = N; for (int i = 2; i < x; i++) { x += ZZZ; // ZZZ = 1 - i if (x % i == 0) ret++; } return ret; } ``` >Input: 15 Output: 4 Explanation: 15 = 15 = 8 + 7 = 4 + 5 + 6 = 1 + 2 + 3 + 4 + 5 * 程式碼解析: ```cpp=5 int ret = 1; int x = N; for (int i = 2; i < x; i++) { x += ZZZ; if (x % i == 0) ret++; } ``` * 假設等差數列 ( 差值為 1 ) 從 $x$ 開始,且個數共有 $k$ 個 則此等差數列為 $$ x, x+1,x+2,...,x+(k-1) $$ 且令其合為 $N$ 則 $$ kx+\frac{(k-1)k}{2}=N \\ \Rightarrow kx =N - \frac{(k-1)k}{2} \\ \Rightarrow kx =N - [1+2+...+(k-1)] \\ \Rightarrow \begin{Bmatrix} N - [1+2+...+(k-1)] \end{Bmatrix}\space mod \space k = 0\\ $$ * 程式碼中的 `i` = 數學推導中的 $k$ * 故 **ZZZ = `1 - i`** * `ret` 為 符合的 $k$ 的個數 * [效能評估](https://leetcode.com/submissions/detail/406894193/): ![](https://i.imgur.com/AQzjrQH.png) ## Q3. 進階題:嘗試更有效率的實作 #### 修改迴圈中止條件 ```cpp int consecutiveNumbersSum(int N) { if (N < 1) return 0; int ret = 1; int x = N; for (int i = 2; i*i < 2*N; i++) { x += 1 - i ; if (x % i == 0) ret++; } return ret; } ``` * $\because k,x \in\mathbb{Z}$ $$ \therefore N - \frac{(k-1)k}{2} >0 \\ \Rightarrow k< \sqrt{2N}\\ \Rightarrow k^2 < 2N\\ $$ * [效能評估](https://leetcode.com/submissions/detail/409429174/): ![](https://i.imgur.com/2LQXk3s.png) #### 數學推導 II ```cpp= int consecutiveNumbersSum(int N) { if (N < 1) return 0; int ret = 1, count; int x = (N >> __builtin_ctz(N)); for (int i = 3; i * i<= N; i+=2 ) { count = 0; while (x % i == 0){ x /= i; count++; } ret *= (count + 1); } if (x != 1) ret <<= 1; return ret; } ``` * 延續 Q4. 的推導 $$ kx+\frac{(k-1)k}{2}=N \\ \Rightarrow 2kx + (k-1)k = 2N \\ \Rightarrow k(2x + k - 1) = 2N \\ $$ * $2N$ 必定可拆解成 **奇數 $\times$ 偶數** * 若 $k$ 為 奇數,則 $k-1$ 為偶數, $(2x+k-1)$ 也為偶數 * ==找到一種 $N$ 的奇因數 $\iff$ 找到一個解== * 若 $k$ 為 偶數,則 $k-1$ 為奇數, $(2x+k-1)$ 也為奇數 * 令 $k' = (2x+k-1)$,則 $k'$ 也是 $N$ 的奇因數 * 因此,==$N$ 的不同奇因數個數 = 此題要的解個數== * $N$ 的不同奇因數個數 * 假設 $N$ 的質因數分解為 $$ N = {2}^{{a}_{0}} \times {{p}_{1}}^{{a}_{1}} \times {{p}_{2}}^{{a}_{2}} \times ... \times{{p}_{r}}^{{a}_{r}} $$ 則 $N$ 的不同奇因數個數為 $$ \prod_{i=1}^{r}({{a}_{i}}+1) $$ * 若 $N$ 為**質數**,則只有 2 種解 $$ 1+1+...+1 \space與\space \left\lfloor\frac{N}{2}\right\rfloor + (\left\lfloor\frac{N}{2}\right\rfloor+1) $$ * 程式碼解析 ```cpp=7 int x = (N >> __builtin_ctz(N)); ``` * 將 $N$ 的 $2$ 因數 全部排除 ```cpp=8 for (int i = 3; i * i<= N; i+=2 ) { count = 0; while (x % i == 0){ x /= i; count++; } ret *= (count + 1); } ``` * 求得 $N$ 的不同奇因數個數 `ret` ( N 為質數時除外 ),請配合上面的數學推導觀看 * `count` = $a_i$ * `i` 代表 奇數 * 逐一檢查所有可能的奇數 * 若發現為 $N$ 的奇因數,則 `N`/`i` 後再檢查一次 * 目前世上並沒有不使用迴圈就能[判定 N 是否為質數](https://zh.wikipedia.org/wiki/%E8%B4%A8%E6%95%B0#%E6%B8%AC%E8%A9%A6%E8%B3%AA%E6%95%B8%E8%88%87%E6%95%B4%E6%95%B8%E5%88%86%E8%A7%A3)的方法 因此,利用 `i * i<= N` 作為可進入迴圈的條件,可在 $N$ 為質數時不進入迴圈 ```cpp=17 if (x != 1) ret <<= 1; ``` * 若 $N$ 為質數,答案應該為 2 * [效能評估](https://leetcode.com/submissions/detail/409441261/): ![](https://i.imgur.com/lpu5jxH.png)