目的: 檢驗學員對 bitwise 的認知
作答表單: 測驗 1-2 (針對 Linux 核心「設計」課程)
作答表達: 測驗 3-5 (Linux 核心「實作」課程)
1
以下程式碼嘗試計算開平方根: (版本一)
對應的測試程式:
編譯方式:
因為呼叫 log2,所以需要連結 libm (math library),即
-lm
選項
我們可改寫上述 i_sqrt
函式,使其不依賴 log2 並保持原本的函式原型 (prototype) 和精度 (precision)。 (版本二)
Digit-by-digit calculation 提及常見的直式開方法的變形。將要開平方的數拆成 2 的冪相加 (若是十進位則為 10 的冪),也就是如下形式:
其中
為 bits pattern,其中 為最高位的 1,最後的形式為
的相加,其中每項就對應到上述程式碼的每一輪迴圈
要判斷並可能減掉的 b
。
原理是將欲求平方根的 拆成:
將其乘開得到
分成幾個部份觀察
原式可整理成
即為所求平方根
顯然
我們的目的是從試出所有 , or 。從 一路試到 ,而求得 只要試驗 是否成立,二種可能狀況:
但我們無法每輪去計算 以試驗 ,因為運算成本太高。一種想法是從上一輪的差值 減去 得到
也就是紀錄上一輪的 來計算
進一步調整,將 拆成
藉由位元運算從 推出下一輪
結合上述方法撰寫演算法來尋求,假設從 開始往下試。
因為是從 開始,所以:
因為 恆大於 ,使用位移操作 到最後必為 ,我們可用 d != 0
作為條件。又 ,算到最後的 即為所求 , 也一併得知。
由上述的推論可知,求出根號需要求得 即為所求 。故寫出迭代的關係,即可求得解。
首先,先判斷輸入的數值,是否符合求解根號的範圍。
此處不考慮針對負數的開根號操作 (涉及虛數的處理),面對 0
和 1
這樣的單調狀況時,直接回傳。且小數不能被整數型別 int
儲存,所以也不用討論。
int m
對應上述推論中的 ,
由於首項 ,可知
__builtin_clz(x)
為 GCC 的內建函式。
Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined.
其功能為找到 x 最高位數前面有幾個 0 , x 為 0 時,未定義。
最後,我們將程式碼改寫如下: (版本三,部分遮蔽)
延伸問題:
__builtin_clz
,使程式不依賴 GNU extension,且提供分支和無分支 (branchless) 的實作。2
針對正整數在相鄰敘述進行 mod 10 和 div 10 操作,如何減少運算成本?
可利用除法原理將 mod 10 和 div 10 合併。根據除法原理:
其中
對應的程式碼:
參考《Hacker's Delight》,採用 bitwise operation 來實作除法,因為 有包含 這個因數,無法完全用 的冪項來表示,因此結果會是不精確的。然而,觀察上面的程式碼後可以發現, tmp
不可能會大於 19
,因此只需要考慮 19
~0
的情況即可。
我們的目標是,得到 tmp / 10
且直到小數點後第一位都是精確的。
為此,我們可提出一個猜想,假設我目標的最大數是 n
, l
則是比 n
還要小的非負整數。假設 ( 是十位數 b 是個位數),且 ( 是十位數, 是個位數),則只要以 算出來的除數在 除以該除數後在精確度內,則 除與該除數仍然會在精確度內。證明如下:
假設 是除數,
於是前述猜想也成立,接下來只需要針對 19
來做討論即可。
接下來只需要找到這之中可以用除法來表示的除數即可。
找除數的方法是,透過 bitwise operation 得到的算式必定是 其中 為非負整數, 正整數。因此只需要透過查看 再配對適合的 即可。
其中, 便是一個可用的解。
接著,嘗試透過 bitwise operation 來配對數值。
關於這段程式碼,以 為例,可發現 ,因此,首先要用 bitwise operation 得到
這時會發生一個問題,即右移後,會有部分位元被捨棄,因此要另行處理。
最後合併前述操作。
考慮 ,也就是右移 個位元
mod 10 就是 tmp
減去 div 10 的結果乘
其中 (((q << 2) + q) << 1)
就是 (,亦即
測試程式:
執行結果:
結果符合預期。
可包裝為以下函式: (部分遮蔽)
使用案例:
延伸閱讀:
延伸問題:
% 9
(modulo 9) 和 % 5
(modulo 5) 程式碼,並證明在 uint32_t
的有效範圍可達到等效。
實作不同於《Hacker's Delight》的程式碼
3
考慮 ilog2 可計算以 2 為底的對數,且其輸入和輸出皆為整數。以下是其一可能的實作: (版本一)
我們可改寫為以下程式碼: (版本二)
亦可運用 GNU extension __builtin_clz
來改寫,注意輸入若是 0
則無定義: (版本三)
請補完程式碼,使其運作符合預期。
AAAA, BBBB, CCCC 是十進位數值,而 DDDD 是表示式。
書寫規範:
x + 1
一類的表示式時,偏好變數在前、常數在後,且二元運算子前後有空白字元延伸問題:
4
Exponentially Weighted Moving Average (EWMA; 指數加權移動平均) 是種取平均的統計手法,並且使經過時間越久的歷史資料的權重也會越低,以下為 EWMA 的數學定義:
接著將上述的數學式展開,可以得到以下的結果
從結果可以得知第前 筆資料為 其加權為 為指數的模樣,這也是為何該手法被稱為 EWMA。
Alpha 的選擇
參照 Exponential Moving Average 和 Frequency Response of the Running Average Filter
該式是個 IIR 的 Low pass filter,於是可得出 DC 值,而 DC 值就是數值的平均值。另外不同的 會對 frequency response 產生影響,造成 lowpass filter 的 passband 的頻寬不同。如下圖所示。
數值 | 頻寬 | 取得正確平均的時間 | 受noise的影響 | |
---|---|---|---|---|
越小 | 越小 | 越慢 | 越小 | |
越大 | 越大 | 越快 | 越大 |
以下是 EWMA 的可能實作,請閱讀其原始程式碼的註解,嘗試補完實作。(顏重用測驗 3
的 ilog2
)
書寫規範:
EEEE
和 FFFF
為表示式延伸問題:
可對照〈圖解傅立葉分析〉
5
延伸測驗 3
,已知輸入必為大於 0
的數值 (即 x > 0
),以下程式碼可計算 ,也就是 ceil 和 log2 的組合並轉型為整數:
請補完,使其行為符合預期,GGGG 是十進位整數。
延伸問題:
x = 0
的狀況,並仍是 branchless