contributed by ???
1
兩個無號整數取平均值考慮以下對二個無號整數取平均值的程式碼:
這個直覺的解法會有 overflow 的問題,若我們已知 a
, b
數值的大小,可用下方程式避免 overflow:
接著我們可改寫為以下等價的實作:
其中 EXP1
為 a
, b
, 1
(數字) 進行某種特別 bitwise 操作,限定用 OR, AND, XOR 這三個運算子。
兩個整數 a
,b
做 /
運算,在 C 語言當中為整數除法,e.g. 5 / 2 = 2.
而對兩數分別作 >>
運算,形同對兩數作 / 2
運算。因此若是遇到奇數,作除法後小數會被捨去。本來 (a+b)/ 2
只捨去一次,現在分別對a
,b
作除法,最多可能被捨去兩次,因此 EXP1
是為了補足被捨去超過一次的情況。
在 a/2 + b/2
之下,探討 a
,b
的奇偶:
a 的最右邊bit | b 的最右邊bit | 小數被捨去次數 | 期望補上的值 |
---|---|---|---|
0 | 0 | 0 | 0 |
1 | 0 | 1 | 0 |
0 | 1 | 1 | 0 |
1 | 1 | 2 | 1 |
期望補上的值,可以看出是 a 的最右邊bit
&
b 的最右邊bit
,取得 a 的最右邊bit
為 a & 1
,因此 EXP1 = (a & 1) & (b & 1)
= a & b & 1
參考 laneser
我們再次改寫為以下等價的實作:
其中 EXP2
和 EXP3
為 a
和 b
進行某種特別 bitwise 操作,限定用 OR, AND, XOR 這三個運算子。
EXP2
和 EXP3
必定包含 a
和 b
字元
參考你所不知道的 C 語言:數值系統篇其中的「算術完全可用數位邏輯」節,a + b
可拆成兩個運算,a & b
算的是進位(carry),a ^ b
算的是相加但不進位,可用以下真值表說明:
a | b | a&b | a^b |
---|---|---|---|
0 | 1 | 0 | 1 |
1 | 0 | 0 | 1 |
0 | 0 | 0 | 0 |
1 | 1 | 1 | 0 |
因此 a + b
= ((a & b) << 1) + (a ^ b)
,而根據題目,要求的是 (a + b)/2
,所以將上述式子 / 2
:(((a & b) << 1) + (a ^ b)) / 2
= (((a & b) << 1) + (a ^ b)) >> 1
= (a & b) + ((a ^ b) >> 1)
,所以 EXP2 = a & b
, EXP3 = a ^ b
TODO:
移動平均(Moving average),又稱滾動平均值、滑動平均,在統計學中是種藉由建立整個資料集合中不同子集的一系列平均數,來分析資料點的計算方法。
移動平均通常與時間序列資料一起使用,以消除短期波動,突出長期趨勢或周期。短期和長期之間的閾值取決於應用,移動平均的參數將相應地設置。例如,它通常用於對財務數據進行技術分析,如股票價格、收益率或交易量。它也用於經濟學中研究國內生產總值、就業或其他宏觀經濟時間序列。
2
前,先來看取絕對值與 min / MAX 問題參考解讀計算機編碼其中的「常數時間實作」篇,提到絕對值的 bit-wise 操作:
對應的實作可以為 (注意此實作 x
不能為 INT32_MIN
):
另外也提到給定兩個有號整數,找出其中較小的數值(注意這實作僅在 INT32_MIN <= (a - b) <= INT32_MAX
才成立):
若 a - b = diff < 0
,則 a
較小,diff >> 31
為 0xffffffff
,與 diff
&
運算後仍為 diff
,回傳 b + diff = a
若 a - b = diff > 0
,則 b
較小,diff >> 31
後為0x00000000
,與 diff
&
運算後為 0x00000000
,回傳 b + 0x00000000 = b
2
找 MAX改寫〈解讀計算機編碼〉一文的「不需要分支的設計」一節提供的程式碼 min,我們得到以下實作 (max):
其中 EXP4
為 a
和 b
進行某種特別 bitwise 操作,限定用 OR, AND, XOR 這三個運算子。
EXP5
為 a
和 b
的比較操作,亦即 logical operator,限定用 >, <, ==, >=, <=, != 這 6 個運算子。
EXP4
, EXP5
必定包含 a
和 b
字元。
EXP5
前有個負號,直覺 EXP5
應為 0
或 1
,而若要找較大者,必定是比較 a
, b
大小,假設 EXP5 = a < b
,再探討下述兩種情況:
a > b
:應該回傳 a = a ^ 0
(a 對 0 xor,形同無作用),所以 0 = (EXP4) & -(EXP5) = (EXP4) & 0
–- 一式。
a < b
:應該回傳 b = a ^ (a ^ b )
(b 對 a 作兩次 xor 形同沒作用),所以 a ^ b = (EXP4) & -(EXP5) = (EXP4) & -1
–- 二式。
解二式 EXP4 = a ^ b
。代入一式 (a ^ b) & 0
也確實等於 0
。
因此答案為:
TODO:
uint32_t
-> int32_t
3
GCDu
,v
兩數皆為偶數(u
, v
任一數二進制的最低位元為 1,條件便無法成立),若皆為偶數兩數都 /= 2
。此為上述註的第三點。u
,v
)v
,因此 COND = v
,且當較小數為 0 時,最大公因數即為另一較大的數。但需要注意的是,在執行上面的 for loop 時,有計算多少 2 的冪
= shift
,使得 為公因數, 因此 RET = u << shift
。__builtin_ctz
改寫 GCD,分析對效能的提升參考你所不知道的 C 語言:數值系統篇其中的「Count Leading Zero」節,__builtin_ctz
算的是一數的二進制表示法中,從最低位開始遇到第一個 1
之前,共有幾個 0
。
知道一數的 ctz(count tailing zero)後,便可以知道其中的因數為 2的多少次冪
。
以相同上述實作的邏輯改寫後:
第 10 到第 16 行,做的是上份實作的 for loop 的功能,算出公因數: 2 的多少次冪
。
第 18 及 21 行,執行的是上份實作兩個 while loop 的功能,若有偶數,將 2 的因數除掉。
參考Linux效能分析工具:Perf,根據以下 main function
:
改寫前實作效能如下:
改寫後實作效能如下:
兩者數據結果差不多,接著把執行次數提升到 100,並多偵測 branch event:
兩份實作 cache miss 約 40.1%,branch miss 約 3.9%,數據結果差異不大,改寫後的程式尚須精進。
TODO:
4
計算 bitmap 中有多少個1, 及分別在第幾位在影像處理中,bit array (也稱 bitset) 廣泛使用,考慮以下程式碼:
此段程式中,pos
代表整個 bitmap
中有多少個 1
。out
紀錄的是每個為 1 的 bit
在 bitmap
的第幾位,這裡可以看出即使 bitmap
為 64 * bitmapsize
,在儲存空間上仍是一維的。
考慮 GNU extension 的 __builtin_ctzll
的行為是回傳由低位往高位遇上連續多少個 0
才碰到 1
。
用以改寫的程式碼如下:
第 9 行的作用是僅保留最低位元的 1,並紀錄到 t
變數。舉例來說,若目前 bitset 為 ,那 t
就會變成 ,接著就可以透過 XOR
把該位的 1
清掉,其他保留 (此為 XOR 的特性)。
若 bitmap 越鬆散 (即 1 越少),於是 improved 的效益就更高。
Hint:需考慮到 -bitwise
的特性
根據此目的,可以先算出 bitset
的 ctz,把 1L
左移 ctz 個,如此可以得出只保留最低位 1
的 bitset
。因此 EXP6 = 1L << __builtin_ctzll(bitset)
。
但若要利用 -bitwise
的特性,需要換個作法:若把 bitset 取負
,與 bitset
差異為除了最低位的 1
以及更低位的 0
相同之外,其他位皆相反。因此若要得到只保留最低位 1
的 bitset
,把 bitset & -bitset
即可。EXP6 = bitset & -bitset
。
第 12 行做的便是把 bitset
最低位 1
消除,讓下一個迴圈再次去找最低位 1
在哪。
TODO: