contributed by < linjohnss
>
無號整數取平均值
兩個無號整數取平均,代表在實做過程中我們不需要考慮 sign bit 的問題,但仍需考慮 overflow 的問題。
從上述程式法可以看到它先將 a
與 b
右移一個位元,代表分別將 a
與 b
除 2。接著將兩者加在一起。
再來,我們需要考慮 a
、b
是否為奇數相加,若果都是奇數則需考慮進位。
因此,EXP1
可以寫成 a & b & 1
來處理奇數的進位。
我們可以透過帶入數字來演示
題目這邊要求只能使用 AND
、OR
、XOR
來實作
若用 c 來實做半加器
SUM = (a ^ b)
CIN = (a & b)
(CIN 需要 *2 達到進位)
a + b = (a ^ b) + ((a & b) << 1)
接著將 a + b
除以 2,可以推得
(a + b) >> 1= (a ^ b) >> 1 + (((a & b) << 1) >> 1) = (a ^ b) >> 1 + (a & b)
因此,得知 EXP2 = a & b
以及 EXP3 = a ^ b
。
編譯器版本:gcc version 9.3.0
使用 -O3
最高級別優化
gcc -S -O3 interger_average.c
優化前:
優化後:
優化前:
優化後:
指數移動平均(英語:exponential moving average,EMA或EWMA)是以指數式遞減加權的移動平均。 各數值的加權影響力隨時間而指數式遞減,越近期的數據加權影響力越重,但較舊的數據也給予一定的加權值。 -維基百科
在 include/linux/average.h 定義了一個 macro,其參數包含:
name
(用於定義 struct 與 helper functions 名稱)_precision
(用於定義固定精度值的小數位數)_weight_rcp
(權重,用於確定新資料與舊資料的加權方式)首先,定義一個開頭為 ewma_
的結構,內部包含一個 unsigned long internal
。
使用前的初始化,檢查各項參數,並令 e->internal = 0
。這能使其在 compile 時期就作檢查。
e->internal >> (_precision)
也就是讀取 internal
的整數值。
此函數為輸入 unsigned long val
並計算 e->internal
的值。
其中 weight_rcp
為 (_weight_rcp),因為 EWMA 的權值是利用指只函數計算。
根據公式 internal = 1/weight_rcp * val + (1 - 1/weight_rcp) * internal 用位移的方式去實做。
不需要分支的 Max
找到兩個有號整數的最校值
diff
a > b
):b
即為最小值。b > a
):b
要加上 diff
,才會變成 a
。 算術位移 (Arithmetic shift) : 補上號數 (sign bit) 也就是最高有效位元的值在左側
找到兩個無號整數的最大值,達到 branchless 的實做。
首先,我們可以根據 a ^ ((EXP4) & -(EXP5))
得知當 a > b
時 ((EXP4) & -(EXP5))
應該要是 0。
反之,當 a < b
時,((EXP4) & -(EXP5))
應該要是 a ^ b
。
題目提示 EXP4
為 bitwise 操作,所以 EXP4
應該是 a ^ b
。
題目提示 EXP5
為 logical operator 操作,代表結果只可能為 0 或 1。
然後我們可以看到他是先作負號運算
接著與 a ^ b
作 AND 運算,因此當 a >= b
時,EXP5
要等於 0x00000000
,反之要等於 0xFFFFFFFF
。將此寫法在精簡,故 EXP5 = a < b
。
實做後發現,因為 -(a < b)
不論 sign 或 unsign 其結果都是 0x00000000
或 0xFFFFFFFF
其中之一,故可以用相同方法實做。
64位元 GCD
總是拿比較大的數值取餘數(相減)比較小的數值,直到其中一個數值為 0 時,另一個數值就是最大公因數。
u, v
都是 0 的話,其最大公因數必為 0。u, v
其中一個為 0 的話,其最大公因數必為不為 0 的那個數。v
當作除數; u
當作被除數,作 u % v
。u % v
)為除數v
,令原本的除數 t
為 u
。v = 0
,u
即為最大公因數。這裡將原本的 Modulo Operator,改為用其他 Operator 實作。
先計算兩個數字的 2 指數最大公因數。
因為我們已經找到公因數 ,後續不會在遇到公因數 2,因此可以先將 2 除掉。
!(u & 1)
是用於判斷是否為偶數。
進入迴圈後,同樣先將 v
變為奇數。
根據輾轉相除法的演算法,這裡將取餘數改成用連續相減來達成。
u, v
相減直到 u > v
時交換(此時 v 為餘數)。COND = v
。而最後回傳的值要在乘上先前除掉的 ,所以 RET = u << shift
Returns the number of trailing 0-bits in x, starting at the least significant bit position. If x is 0, the result is undefined.
回傳 interger x 最末端 0 的個數。
因此,我們可以從上述程式法發現__builtin_ctz
可以用於偶數除法的部份。
bit array
此函式的功能是將一個長度為 bitmapsize
的 64 位元無號數陣列的所有 1 記錄下來,並用陣列 out
將這些 1 依序記錄下來,最後回傳 1 的數量。
為了找到陣列中的每一個 1,它利用 for 迴圈一個一個慢慢找,然而我們可以用 __builtin_ctzll
來找到最低位元 1 的位置來改寫。
首先,我們看到 t
會在後面與 bitset
作 XOR 運算,代表其作用可能是清掉 bitset
的 1。因此,我們可以得知 EXP6
應該是取得最低位元 1 的位置,故 EXP6 = bitset & -bitset
。
Fraction to Recurring Decimal
leetcode 166. Fraction to Recurring Decimal
計算一個分數的十進制成果。
使用 linux kernel 的 list API 來實做 hash table。
find
這個 function 會根據 key
找到 hash table 中對應的 key
是否存在,若不存在則回傳 -1
。
若分子或分母任意一個為 0 則回傳 0。
改用 long long 型態來處理,避免 overflow。
先處理負號的情況,若其中一個為負,則在開頭加上 ' - ',後續都以正數處理。
這裡處理除法以及取餘數的部份,若不是整除 (remainder !=0
),則代表要處理小數部份,反之可以直接回傳相除的成果。
接來就是確認餘數(remainder
)是否為循環小數
remainder
是否存在remainder
加到 hash table 中,並紀錄 i
(位數)。
list_add
&heads[remainder % size]
pos--
(remainder * 10) / d
,並將商加到 q
中remainder
為餘數,重複上述動作直到 remainder
為 0__alignof__
__alignof__
的功能是能夠指定物件在記憶體中的對齊,將資料儲存在資料大小倍數的位址時,可以改善 cache 的效能。
我們可以利用 struct
的對齊性質來實作 __alignof__
。首先,可以看到 struct
內部包含一個 char c
以及一個 t _h
,目的是找到型別 t
對齊時所需的位元大小,因此M = _h, X = 0
計算頭尾相減即為答案。
FizzBuzz