# Linux 核心專題: 位元運算 > 執行人: NeedToDebugMyLife ### Reviewed by `jserv` 說好的進度呢? ### Reviewed by `Denny0097` >圖片顯示錯誤,可能是權限問題。 ### Reviewed by `ginsengAttack` 幾何平均是否可以用2項式定理實作。 ## TODO: 以定點數實作開平方根 ### `說明` #### 定點數 定點數是指用固定整數位數表達分數的格式,考慮一個 32 bits 的數,如果使用 Q16.16 格式,就代表這個數的前 16 位元用於表示整數部分,後 16 位元用於表示小數部分  例如: | 十進位 | 二進位 | 定點數(Q16.16) | | ------ |:---------:|:---------------------------------:| | 1.5 | $(1.1)_2$ | 0000000000000001 1000000000000000 | #### 定點數運算 ##### `加法` `減法` 和整數加減法相同 ##### `乘法` 考慮 2 浮點數 $f_1$、$f_2$,分別對應 Q16.16 定點數 $n_1$、$n_2$。 兩定點數可表示為 $$ \begin{align} n_1=f_1\times 2^{16} \\ n_2=f_2\times 2^{16} \end{align} $$ 兩定點數相乘後得到 $$ \begin{align} n_1\times n_2 &= (f_1\times 2^{16})(f_2\times 2^{16}) \\ &= f_1\times f_2\times 2^{32} \\ \end{align} $$ 但將兩浮點數相乘後再表示為定點數卻會得到 $$ \begin{align} n_{f_{1\times 2}} &= \left(f_1\times f_2\right)\times 2^{16} \\ &\neq n_1\times n_2 \\ \end{align} $$ 所以還需要再將 $n_1\times n_2$ 的結果除以 $2^{16}$ <small>(右移 16 位元)</small> 才會得到正確的乘法運算結果,即 $$ n_1\times n_2 = f_1\times f_2\times 2^{-16} $$ ##### `除法` 和 `乘法` 類似 兩定點數相除後得到 $$ \begin{align} n_1\div n_2 &= (f_1\times 2^{16})\div(f_2\times 2^{16}) \\ &= f_1\div f_2 \end{align} $$ 但將兩浮點數相除後再表示為定點數卻會得到 $$ \begin{align} n_{f_{1\div 2}} &= \left(f_1\div f_2\right)\times 2^{16} \\ &\neq n_1\times n_2 \end{align} $$ 所以還需要再將 $n_1\times n_2$ 的結果乘以 $2^{16}$ <small>(左移 16 位元)</small> 才會得到正確的乘法運算結果,即 $$ n_1\div n_2 = f_1\div f_2\times 2^{16} $$ <br> #### 牛頓法求根號 [參考資料](https://hackmd.io/@sysprog/sqrt#%E7%89%9B%E9%A0%93%E6%B3%95%E6%B1%82%E5%B9%B3%E6%96%B9%E6%A0%B9) 牛頓法迭代公式: $$ x_{n+1}=x_n-\frac{f(x_n)}{f'(x_n)} $$ 考慮求 $\sqrt {n}:$ 可以將原題目轉換為求 $f(x)=x^2-n$ 的根 (因為 $x=\sqrt n$ 為函式 $f(x)$ 的解)後得到: $$ \begin{align} f(x)&=x^2-n \\ f'(x)&=2x \end{align} $$ 接著將 $f(x)$ 和 $f'(x)$ 代入到迭代公式 得到: $$ \begin{align} x_{n+1} &=x_n-\frac{x_n^2-n}{2x_n} \\ &=x_n-\frac{1}{2}\left(\frac{{x_n^2}-n}{x_n}\right) \\ &=x_n-\frac{1}{2}\left(x_n-\frac{n}{x_n}\right) \\ &=\frac{1}{2}\left(x_n+\frac{n}{x_n}\right) \end{align} $$ ### 情境 1 > 輸入是 IEEE float (單精度),輸出是 int 型態 (採用 LP64 data model) > 針對上述情境,皆不可使用 FPU,只能藉由定點數予以計算,務必降低誤差並比較 glibc 的效能表現,提出改進方案 浮點數結構: $n=2^S*M*2^E=M*2^E$ :::info 由於要求根號,所以只考慮 f 為正的情況 (即 S=0) ::: (負數以例外狀況處理) 開平方後得到 $$ \begin{align} \sqrt n&=\sqrt {M*2^E} \\ &=\sqrt M*{2^\frac{E}{2}} \\ \end{align} $$ 如果 $E$ 是偶數,即 $E=2k$,則 $\sqrt n=\sqrt M*{2^k}$ 如果 $E$ 是奇數,即 $E=2k+1$,則 $\sqrt n=\sqrt M*\sqrt 2*{2^k}=\sqrt {2M}*{2^k}$ 可以發現只需要計算出 $\sqrt M$,就可以快速得到 $\sqrt n$ <br> 在計算前,我們還需要先得到浮點數的 $E$ 和 $M$  * `E` : 為第 23 位元到第 30 位元,所以需要右移 23 位,並且保留需要的 8 位。因為是讀出操作,所以需要再減去 127 * `M` : 為第 0 位元到第 22 位元,不需要做任何移位操作,但仍需要保留需要的 23 位 ```c E = ((n >> 23) & 0xFF) - 127; M = n & 0x7FFFFF; ``` 因為浮點數不能直接做位移運算,所以需要使用 union 將浮點數的 32 位元表示讀取到一個 uint32_t 中 ```c typedef union { float f; uint32_t i; } fi_union; fi_union u = f; ``` 使用 `u.i` 求出 $E$ 和 $M$ (需要額外補上整數部分的 `1`) ```c int32_t E = ((u.i >> 23) & 0xFF) - 127; uint32_t M = u.i & 0x007FFFFF | (1 << 23); ``` 因為只能使用定點數做運算,所以計算 $\sqrt M$ 前還需要將 $M$ 轉換成定點數表示。但因為 $M$ 為 Q1.23,所以使用 Q32.32 格式來表示會比較適當 (即 32 位元為整數部分,後 32 位元為小數部分)。 ```c uint64_t M_fixed = (uint64_t) M << 9 ; ``` 前面提到, 如果 $E$ 是偶數,$\sqrt n=\sqrt M*{2^k}$ 如果 $E$ 是奇數,$\sqrt n=\sqrt {2M}*{2^k}$ 所以計算 $M$ 前還要檢查 $E$ 是否為奇數。 ```c // multiply M by 2 if E is odd if (E & 1) M_fixed <<= 1; sqrt_M_fixed = sqrt_Newton_64(M_fixed); ``` 使用牛頓法計算 $\sqrt M$ $$ x_{n+1}=\frac{1}{2}\left(x_n+\frac{n}{x_n}\right) $$ ```c uint64_t sqrt_Newton_64(uint64_t fixed) { // using 1.5 for x0 uint64_t x = (3ULL << 31); // iteration = 6 for (int i = 0; i < 6; ++i) { if (x == 0) return 0; unsigned __int128 dividend = (unsigned __int128)fixed << 32; uint64_t div = dividend / x; x = (x + div) >> 1; } return x; } ``` 最後使用 $\sqrt M$ 和 $k=\frac{E}{2}$ 計算出 $\sqrt n$,並強制轉型為 `int` ```c uint64_t result = sqrt_M_fixed << (E >> 1); ``` 因為回傳的值為 `int`,所以只需要保留前 32 位元 ```c uint64_t result = (sqrt_M_fixed << (E >> 1)) >> 32; ``` 整理後得到 ```c uint64_t sqrt_M_fixed = sqrt_Newton_64(M_fixed); uint64_t result = sqrt_M_fixed >> (32 - (E >> 1)); ``` 回傳結果並強制轉型為 `int` <br><br> ### 情境2 > 輸入是 IEEE float,輸出也是 float 型態 > 針對上述情境,皆不可使用 FPU,只能藉由定點數予以計算,務必降低誤差並比較 glibc 的效能表現,提出改進方案 執行流程和[情境1](#%E6%83%85%E5%A2%83-1)大致相同,差別在於最後要將 result 存為 `float` 而不是 `int`。 在得到計算結果 (`sqrt_M_fixed`) 後,需要從結果重新提取新的 $E$ 和 $M$ * `M_new` : 因為原本提取出的 `M` 是一個落在 $[1.0, 2.0)$ 區間的數,所以開根號後的值 (`sqrt_M_fixed`) 也必定落在 $[1.0, 2.0)$ 區間,滿足符點數 mantissa 的規範,因此可以直接將開根號結果的小數部分做為浮點數的 mantissa (需要對齊),對齊部分為 $32 - 23 = 9$ 個位元。  對齊後去除多餘的整數部分 (`1`) ```c uint32_t M_new = (sqrt_M_fixed >> 9) & 0x7FFFFF; ``` * `E_new` : 前面提到,$\sqrt M$ 是一個落在 $[1.0, 2.0)$ 區間的數,以 1.OOO 來表示,再代回原算式: $$ \begin{align} \sqrt n&=\sqrt M*{2^k} \\ &=1.OOO*{2^k} \end{align} $$ 可以發現這就是一個浮點數表示法,所以浮點數的 exponent 就等於 $k$ (`E >> 1`)。但因為是寫入操作,所以要再加上 127 ```c int32_t E_new = (E >> 1) + 127; ``` 串接 `E_new` 和 `M_new`  ```c fi_union result; result.i = (E_new << 23) | M_new; ``` 回傳 `result.f` <br><br> ### 情境3 > 輸入和輸出都是 double (倍精度) 型態 > 針對上述情境,皆不可使用 FPU,只能藉由定點數予以計算,務必降低誤差並比較 glibc 的效能表現,提出改進方案 <br><br> ## TODO: 計算幾何平均數 ### `說明` 幾何平均數 (Geometric Mean): $$GM=\sqrt[n]{x_1\cdot x_2\cdot x_3\cdot \ldots\cdot x_n}$$ 計算的方法很簡單,但問題也很明顯。 * 如果數值都是小於 1 的小數,乘積就會趨近於 0,超出浮點數可表示的精度 * 如果數值都是很大數,乘積就會很容易超出最大可表示的範圍 要解決溢位的問題,可以將原本幾何平均數的公式取個對數 即: $$ln(GM)=ln\left(\sqrt[n]{x_1\cdot x_2\cdot x_3\cdot \ldots\cdot x_n}\right)=\frac{1}{n}\sum\limits_{i = 1}^nln\left({x_i}\right)$$ 這樣就能將原本的連乘運算轉成連加運算。 但因為要求的值是 $GM$,不是 $ln(GM)$,所以還要再做個指數運算 $$GM=exp(ln(GM))=exp\left(\frac{1}{n}\sum\limits_{i = 1}^nln\left({x_i}\right)\right)$$ 才能得出原本的 $GM$ ### 情境1 > 給定若干 int 型態的資料,在 O(1) 空間複雜度的前提,計算幾何平均,要考慮到 overflow,並探討限制和改進方案 ### 情境2 > 改為 float 型態,其餘同情境 1 > 針對上述情境,皆不可使用 FPU,只能藉由定點數予以計算,務必降低誤差並比較 glibc 的效能表現,提出改進方案 ## TODO: 以定點數實作快速傅利葉轉換 (FFT) > 務必降低誤差並持續提出改進方案 ## TODO: 探討上述在 Linux 核心的應用 > 搭配課程教材,理解開平方根和幾何平均和 FFT 如何應用於 Linux 核心,予以探討其實作手法
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up