contributed by < ranvd >
根據 Digit-by-digit calculation 的方法,一個數字 可以表示為 ,假設以 10 為底的話 其中 或 ,同樣的方式可以推廣至以 2 為底。如果將整個公式展開的可以表示成下面公式
接著假設 ,其中 代表 或 ,因此可以列出遞迴關係式 。在 從 的迭代過程中判斷 是否大於 即可判斷 是 0 或 。但為了不要每次都重新做平方運算,因此定義 ,並列出遞迴關係式 ,其中 。
又可以將 拆解成 其中 ,從這裡可以得知,當 時,,因此 為 ,接著可以推導出 與 的遞迴關係式。
因此從上面可以看到 ,其中 因此在初始化時須將其設為不超過 x 且為 4 的冪的數值。因此才會使用 int d = 1UL << ((31 - __builtin_clz(x)) & ~1UL)
做初始化。由於 因此可以透過判斷 來判斷 ,並根據上述的公式來更新數值。
__builtin_clz
依據上述題到的內容,int d = 1UL << ((31 - __builtin_clz(x)) & ~1UL)
是要找出不大於 x 的最大 4 的冪。因此透過 fls 就可以正確的找出正確的數值,如下更動。
透過判斷 i
是否大於 來減少運算的次數,之所以 使用 16, 8 ,4, 1 的方式是使用了二分搜尋的概念,減少需判斷的次數。
在 include/linux/log2.h 中有 log2 相關的實做。使用 fls
函式實做。
ceil_ilog2
與 ilog2
的不同在於,ceil_ilog2
是 而 ilog
是 。這導致 ceil_ilog2 不能直接使用 fls 找出最高為 1 的 bit。例如 ceil_ilog2(0b1000)=3
而 ceil_ilog2(0b1001)=4
。
因此可以在取 log 之前先將數值減 1,這可以讓數值剛好是 的資料取 log 後比原本還要小 1,但對於數值不等於 的資料則不會有影響。因此在取完 log 後再無條件加 1 就可以得到 。
參考 include/linux/log2.h 可以得知當 n == 0
時,ilog2 回傳的數值為 -1。因此以下改進也會遵循此規則。
目前的實作有兩個缺陷,第一個是 n==0
時,函式的回傳值為 32,應該要為 -1;第二個是 n==1
時,函式的回傳值為 1,應該為 0。
根據上述的規則套用到現在的程式碼上,可以理解為當 n==1
時,最後回傳時不需要加 1;同時,因為 n==0
時必須回傳 -1,因此可以考慮讓 n==0
時的運算結果等於 1,接著再做二補數,也就是讓 0 直接取 log 後在加上 1,最後再進行反轉,即可得到 -1。
代補