contributed by < david965154
>
1
第一題利用 2 的冪可以組成任何數的機制 (如同 4 bits 二進位 1111 可得出 0-31 之間的值,所做的僅需是計算每一位的值並做「總和」) ,而利用此方法可以大幅省去計算成本 -> ,而實際上去執行時可以看出 msb 前面一半的值是完全不會用到的,也就是取 為例, a 會出現 32、16、8、4、2、1 ,不過 32、16、8 不可能會達成 (result + a) * (result + a) <= N
的條件,因此我將 msb 先右移 1 位再去做計算,並在每一次比較皆進行計算比較次數的行為,使比較次數減少約一半。
版本二
大致與版本一相同,僅差在不依賴函式 log2 並實作該功能。
版本三
直接跳到原式整理的地方
將 使用二進位表示
為二進位第 n 個 bit 代表的值
即為
方法一、二即直接透過從 試驗到 湊出目標平方根 二進位表示
試驗的過程為判斷 是否成立,而 ,因此會去看 加上 是否達成條件,若成立 即加上 ,反之則不加,以此逐漸逼近 。
數學式表示如下:
但是平方本身計算成本太高,因此往前一步所得去處理,不斷利用上個計算所得來取得這次計算所得,即遞迴式。
方法為此次差值 可改為上次差值 減去 (即 之變化值,由是否加入 而決定)
數學式表達如下:
可以看到上數學式出現 而 與 2 的冪有關,可以作左右移達成計算,因此進一步作處理,將 拆解成 與 。
再將 與 寫成遞迴的方式:
再來若要湊出 的二進位表示法可用迴圈計算,而初始則先從:
這裡應為 而非原題目之
迴圈結束條件:
因為 恆大於 0 ,使用位移操作 到最後必為 0 ,因此可以 d!=0
作為中止條件。而 ,而 ,因此 即為所求。
ffs
/ fls
取代 __builtin_clz
__ffs
: find first bit in word
__builtin_clz
: Returns the number of leading 0-bits in x
先使用一變數 tmp
等於 x
,由於 __ffs
在 lsb 一遇到非 0 bit 則停止,因此我使用一變數 msb 不斷累加 __ffs
回傳值 i
,同時tmp
不斷右移 i
直到 tmp
等於 0 ,不過須注意的是如果傳入 __ffs
之值最低 bit 為 1 時,將會回傳 0 而使 tmp
無法右移,將導致無窮迴圈,因此而外加上 1 ,代表會加上前方 bit 1 以避免此情況。
Linux 核心原始程式碼 linux/lib/math/int_sqrt.c
這裡用途為透過 request queue depth
裡的成員 scale_step
動態決定 cur_win_nsec
,目的為調整執行速度及頻率,以便管理系統效能及資源。
調整方式為將 win_nsec
左移四位 再除上 rqd->scale_step + 1) << 8
之開跟號後的值。
2
這裡要從最低位開始進行 mod 10 和 div 10 操作,不過因為一般整數格式不易取最低位值進行處理,因此以 char 格式作為輸入,再來看迴圈中
int tmp = (b[i] - '0') + (a[i] - '0') + carry;
, ASCII 中,數字為 '0' 到 '9' ,扣除 '0' 範圍剩 '1' 到 '9' ,而 a 、 b 各一個再加上 carry ,因此最大為 19 ,也就是 tmp
不可能會大於的值。
除法因為 10 有包含 5 這個因數,無法完全用 2 的冪項來表示,因此結果會是不精確的。
因此僅能以接近 10 的倒數又同時符合直到小數點後第一位都是精確的條件下達成。
假設 是除數,前面提到的 19 作範例,則:
再來透過 bitwise operation 實作除法得到的除數 必定是 其中 為非負整數, 正整數。因此只需要透過查看 再配對適合的 即可。
如:
因此除數 可寫成
之所以為 3
, 1
, 0
是因為這些的 2 的冪可以組成 13 ()
首先要用 bitwise operation 得到
為得到 ,因此左移 3 位。
d0
, d1
, d2
皆為保留右移後被捨棄的部分位元。
因此處選擇 ,將 d0
, d1
, d2
加上去後再右移 7 位做除以 128 的動作,得到除數 。
餘數則透過 。
包裝函式:
這裡 q
為右移三位 () 之前的 div
,又透過 & ~0x7
取第四位 () ,加上 (*div << 1)
得到 *div。
% 9
(modulo 9) 和 % 5
(modulo 5) 程式碼實作不同於《Hacker's Delight》的程式碼
3
ilog2 計算以 2 為底的對數
以二進位表示法在右移時計算同時是做 的動作,因此可以計算 log2 ,不過因為一次以一步的長度計算太慢,因此步數由 2 的冪開始。
不過這樣計算又使程式碼過於冗長,因此改以 __builtin_clz
直接計算頭部最後一個零在哪個位置以計算 log2 。
觀察 8 bits 二進位 0b01001110
,在頭部第一個 1 後不管有多少個 1 都不會造成此二進位數翻倍,因此不用計算後面有多少 1 。
Linux 核心原始程式碼 linux/include/linux/log2.h
這裡有點類似上一題將 __builtin_clz
改成使用 fls
的 macro 。
應用案例 linux/fs/ocfs2/ioctl.c
這裡是利用 __ilog2_u32
尋找 index
,再對 hist
成員 fc_chunks
及 fc_clusters
的位置index
做特定處理。其為 Oracle Cluster File System 2 (OCFS2) (一般性用途的日誌檔案系統) 的直方圖更新函式。
ioctl 系統呼叫的作用 參考 Linux Ioctl internel
當用 read/write 不能完成某一功能時,就用ioctl。
4
觀察 EWMA 計算式
: avg->weight
: val
: avg->internal
即
就是上面給的數學式經過提取並變換順序。
Linux 核心原始程式碼 linux/mm/ksm.c
Linux 核心原始程式碼 linux/include/linux/average.h
分析程式碼的精確度,用實際數值帶入。
可以發現基本上 ewma
與 DECLARE_EWMA
大同小異,差異僅為 DECLARE_EWMA
使用了位元運算,且固定 weight
, DECLARE_EWMA
先將 factor 及 weight 取了 ilog2 以加速運算,而 ewma
因為應用於 KVM (花了很多時間在理解並寫下 筆記 ),掃描間隔太短,無法捨棄太多小數點,因此無法與 DECLARE_EWMA
使用相同寫法。
以上兩邊都是 EWMA 不過計算方式明顯不同,我也不太了解為什麼 。
應用案例 linux/drivers/net/wireless/mediatek/mt7601u/phy.c
RSSI 是衡量設備從接收端接收信號能力的指標
參考 UniFi Network - 了解和使用最小 RSSI
主要目的是幫助客戶端在兩個臨近的 AP (Assess Point) 之間漫遊。它可以防止設備在較弱的信號強度下“卡住”,一直連接初始 AP,而不會漫遊到性能更佳的新 AP。一旦信號低於設置的最小 RSSI 值,初始 AP 會踢掉客戶端,以便它可以重新連接到新 AP。
而 EWMA 使經過時間越久的歷史資料的權重也會越低,用 EWMA 將過去一段時間信號強度乘上不同的權值來計算評估信號強度,若發現信號強度不足,則變更 AP 的選取。
5
先透過 x 得到最高位 bit 再透過遞減逐漸逼近正確 log2 值。
(r | shift | x > 0x2) + 1
r
負責 bit >= 2 , shift
負責 bit = 1 , x > 0x2
則負責 bit = 0。
最後加 1 則是因為 ceil 。
x = 0
的狀況,並仍是 branchless這裡我想法是若 x==0
則將 1 右移 3 位 (只要數值大於 2 就好,因為原本輸入為 0 時輸出結果為 32 ,因此需右移 5 位,而 1 右移兩位為 4 ,不足以將 32 歸 0) ,最後透過右移 mask 將 32 歸 0 (原不等於 0 的值不會受影響)。
Linux 核心原始程式碼 linux/tools/include/linux/log2.h
應用案例 linux/tools/perf/util/print_binary.c
file 的翻譯是「檔案」,而非「文件」(document)
目的是以指定的格式將二進位資料輸出到檔案中。
這段函式雖然用到 ceil 和 log2 的組合,不過我不知道為什麼要這樣做,唯一的想法是為了要控制在 2 的冪以利用位元運算達成更快的計算是否達到該行結尾。
1
漢明距離
即兩個等長字串之間的 Hamming distance ,是兩個字串對應位置的不同字符的個數。
而計算方法為 __builtin_popcount(x ^ y)
若輸入有 n 個字串,則這 n 個字串會與所有 n 個字串做 Hamming distance 的計算。因此任意兩的字串會以不同的順序各被計算一次,因會被重複計算,要右移一位以得到正確值。
寫出避免重複運算的版本。
所有 Number 中從第 0 個 bit 開始計算全部該 bit 的 Hamming Distance
因此目標被設定為時間複雜度 因 bit 數不被輸入數量影響,只與作業系統有關。
2
n 的 2 進位表示法
透過 n = popcount(n & 0x55555555) - popcount(n & 0xAAAAAAAA)
計算位元和, 5 的二進位表示為 0101 (偶數位置 0 、 2 ),而 A 為 1010 (奇數位置 1 、 3 )
再透過定理
將
n = popcount(n & 0x55555555) - popcount(n & 0xAAAAAAAA)
變為
n = popcount(n ^ 0xAAAAAAAA) - 16
+ 23
:原先是 -16
,這裡因為避免 n 變為負數,因此加上一足夠大的 3 的倍數。
接著重複應用於得到的 n 上直到 n 介於 0~2 (類似不斷除以 3)。
最後為了避免 n 為負,將 n 右移 31 位(原本的正數則不受影響)並 & 3
再加上原本的 n 。
tic-tac-toe
3