contributed by < Ahsen-lab >
1
對二個無號整數取平均值的程式碼:
作答區
EXP1 = a & b & 1
兩個 uint32_t
無號整數 a
與 b
取平均值:
無號整數右移一個 bit
其實就相當於除以 2
的操作,所以上述算式可改寫為 (a >> 1) + (b >> 1)
,但是這裡要處理一個例外狀況,當 a
與 b
都是奇數的時候,a
與 b
的最低位 (最右邊的位元) 的 1
在右移的過程中會被消去,導致平均數會等於 (a >> 1) + (b >> 1) + 1
。
為了要處理這個例外狀況,所以在實作中 (a >> 1) + (b >> 1)
會加上 (a & b & 1)
,當 a
與 b
都是奇數時,(a & b & 1) == 1
,如此便可處理例外的情況。
作答區
EXP2 = a & b
EXP3 = a ^ b
當 a
與 b
都是無號整數,a ^ b
代表相加但不進位,(a & b) << 1
代表進位但不相加,代入到 (a / 2) + (b / 2)
。
(a & b)
代表 (a / 2)
和 (b / 2)
進位但不相加。
(a ^ b) >> 1
代表 (a / 2)
和 (b / 2)
相加但不進位。
所以要計算 (a / 2) + (b / 2)
可以代換成(a & b) + ((a ^ b) >> 1)
。
(a >> 1) + (b >> 1) + (a & b & 1)
-O1
-O2
2
改寫〈解讀計算機編碼〉一文的「不需要分支的設計」一節提供的程式碼 min,我們得到以下實作 ( max ):
作答區
EXP4 = a ^ b
EXP5 = a < b
首先比較 a
與 b
的大小,若 a < b
,-(a < b) == -(true) == -1 == 0xffffffff
,而 (a ^ b) & 0xffffffff == (a ^ b)
,a ^ (a ^ b) == b
。
所以若 a < b
,則會回傳 b
。
若 a >= b
,-(a < b) == -(false) == -0 == 0x0
,而 (a ^ b) & 0x0 == 0
,a ^ 0 == a
。
所以若 a >= b
,則會回傳 a
。
3
考慮以下 64 位元 GCD (greatest common divisor, 最大公因數) 求值函式:
改寫為以下等價實作:
作答區
COND = v
RET = u << shift
程式碼解釋:
檢查 u
或 v
是否為 0
,若其中一數為 0
,回傳另一個數,任何數與 0
的最大公因數就是它自己本身。
若 u
, v
都為偶數,判斷兩數之間可以共同被整除的最大非零偶數,shift
代表除以幾次 2
。
若 u
仍為偶數,將其重複除以 2
讓其變為奇數,因為前面已經找過兩數之間的最大偶數因數,所以不需要再考慮偶數的部分。
第 12~13 行,若 v 仍為偶數,將其重複除以 2 讓其變為奇數,因為前面已經找過兩數之間的最大偶數因數,所以不需要再考慮偶數的部分。
第 14~21 行,確保 u
比 v
大,用迴圈讓 u
除以 v
,u
代表上一次相除的除數,v
代表上一次相除的餘數。當 v==0
時,u
會等於 u
, v
兩數之間可以共同被整除的最大奇數。
某個無號整數左移一次代表乘以 2
,兩數的最大公因數為 u * 2 * shift
(u
代表兩數之間可以共同被整除的最大奇數),可以改寫為 u << shift
,代表 u
乘以 shift
次 2
。
4
在影像處理中,bit array (也稱 bitset) 廣泛使用,考慮以下程式碼:
使用 __builtin_ctzll
改寫的程式碼如下:
作答區
EXP6 = bitset & -bitset
設定一個變數 pos
用來紀錄 bitmap
中非零 bit
的個數。
第 6~14 行,使用迴圈來遍歷 bitmap
,bitset
代表 bitmap
中每個 index
的值。
其中第 9 行的作用是找出目前最低位元的 1
,並紀錄到 t
變數。舉例來說,若目前 bitset
為 ,那 t
就會變成 。在二補數中,對 bitset
取負號表示 ~bitset + 1
,bitset & (~bitset + 1)
剛好可以保留最低位元的 1
,再將結果存入 t
。
接著使用 __builtin_ctzll
找出 t
的 Count Trailing Zeros (ctz),再將結果存入變數 r
,並把每個非零 bit
的位置存入 out
array 中,最後用 XOR 將 bitset
中最低位元的 1
清掉。
不斷重複上述的動作,最後 improved function 會回傳 pos
,而 out
中會儲存所有非零 bit
的位置。
5
LeetCode 166. Fraction to Recurring Decimal 的可能實作:
作答區
PPP = pos--
MMM = list_add
EEE = &heads[remainder % size]
第 7~11 行,設定 list 中的 element 的結構,包含 key
和 index
。
第 13~22 行,這裡 heads
代表一個 hash table,size
代表 hash table 的 size,key
代表 list 中 element 的 key
。在 find function 中,hasa 代表包含有 key
的 list 的 head,使用 list_for_each_entry
遍歷該 list,若 list 中的 node->key
與 key
相同,則回傳 node->index
,若沒有相同的 key
,則回傳 -1
。
第 26~28 行,result
是一段記憶體空間,用來儲存計算的結果,p
指向 result
的第一個位置。
第 30~49 行,處理分子或分母等於零的狀況,若分母為零,返回 NULL
,分子為零返回 0
,以及處理分子或分母為負號的狀況,若是負號則轉為正號。
第 51~63 行,判斷是否為負數分數,若是,result 中第一個字元為 -
,接著處理整數的部分,將分子除以分母,把商存入 result,並在後面加入小數點 .
。
第 68~75 行,decimal
是一段記憶體空間,用來儲存小數點的部分,q
指向 decimal
的第一個位置。另外,初始化一個 size 為 1333 的 hash table,其中包含 1333 個 list。
第 77~97 行,使用迴圈來計算小數,首先將餘數傳入 find function 中查詢是否有重複出現的餘數:
若 pos >= 0
代表從小數點後 pos
位開始出現循環小數,所以先用迴圈將小數點後到 pos
之間的數字填入 result
,接著加入左括號,然後開始填入循環小數的數字,填完後加入右括號,並在結尾加上 '\0'
,最後回傳 result
。
若 pos < 0
表示還未碰到循環小數,這時會初始化一個新的 rem_node
,並將當前的 remainder
及 i
存入 rem_node
,i
代表現在計算到小數點後幾位,接著用 list_add
將 rem_node
放入 hash table 中的第 remainder % size
個 list 的開頭,最後將 (remainder * 10) / d
的值存入 decimal
,並將 remainder
更新為 (remainder * 10) % d
。
第 99~100 行,若都沒有遇到循環小數,那就將 decimal
中的值複製到 result
中,並回傳 result
。
6
__alignof__
是 GNU extension,以下是其可能的實作方式:
7
考慮貌似簡單卻蘊含實作深度的 FizzBuzz 題目:
以下是利用 bitwise 和上述技巧實作的 FizzBuzz 程式碼: (bitwise.c
)
作答區
KK1 = div3
KK2 = div5
KK3 = div3 << 2
is_divisible
function 是用來判斷 M
是否可以被 n
整除,或是可用來判斷 n
是否是某數的倍數,判斷的公式為 n * M <= M - 1
。
若要判斷 n
是否為 x
的倍數,則 M
的值為 (uint64_t 最大值) / x + 1
。假設要判斷 n
是否為 3
的倍數,M
就代入 UINT64_C(0xFFFFFFFFFFFFFFFF) / 3 + 1
。因為 n
一定是正整數或零,所以只有在 n * M == 0
時,n * M <= M - 1
才會成立,也就是說 n
一定要是 3
的倍數,n * UINT64_C(0xFFFFFFFFFFFFFFFF) / 3 + 1
才會 overflow 變成 0
,is_divisible
才會返回 true
。
使用迴圈判斷 1~100,若是 3 的倍數 div3 == 1
,若是 5 的倍數 div5 == 1
。
length
代表輸出字串的長度:
length
等於 (2 << 1) << 0 == 4
length
等於 (2 << 0) << 1 == 4
length
等於 (2 << 1) << 1 == 8
length
等於 (2 << 0) << 0 == 2
&"FizzBuzz%u"[(8 >> div5) >> (div3 << 2)]
代表從第幾個字元開始的字串 (題目第 17 行的程式碼有誤,應改為上述第 18 行所示):
(8 >> 0) >> (1 << 2) == 0
,也就是字串 FizzBuzz%u
。(8 >> 1) >> (0 << 2) == 4
,也就是字串 Buzz%u
。(8 >> 1) >> (1 << 2) == 0
,也就是字串 FizzBuzz%u
。(8 >> 0) >> (0 << 2) == 8
,也就是字串 %u
。將上述參數代入
即可得到題目所描述的結果。