contributed by < Ahsen-lab
>
1
對輸入的 64-bits 無號整數 x
,將其最高位元的 1
到最低位元中的所有位元都設定為 1
,最後回傳 x + 1
,即可得到 大於 且最接近 x
的 2 的冪的值。
舉例來說
最高位元到最低位元中的第一個 1
是位在第 62 位元,首先重複 7 次 x |= x >> 1
的操作,將 x
中最高位元的 1
右側的 7 個位元都設定為 1
接著執行 x |= x >> 8
的操作,將第 55 位元往右的 8 個位元都設定為 1
接著執行 x |= x >> 16
的操作,將第 47 位元往右的 16 個位元都設定為 1
接著執行 x |= x >> 32
的操作,將第 31 位元往右的 32 個位元都設定為 1
最後回傳 x + 1
如此便可得到大於且最接近 x
的 2 的冪的值。
當輸入的 x
是 2 的冪時 ( 例如 2
、 8
、 16
等等 ) ,上述程式會回傳錯誤的答案,因為題目要求的是找出最接近且 大於等於 2 的冪的值,而上述程式只會回傳最接近且 大於 2 的冪的值。
換句話說,如果輸入的 x
是 2 的冪時,只需回傳 x
本身,不需要再尋找比它更大的 2 的冪。因此,上述程式在 x
是 2 的冪時會回傳錯誤的答案。
__builtin_clzl
改寫2
給定一個整數 ,將 1 到 的二進位表示法依序串接在一起得到一個二進位字串,回傳其所代表的十進位數字 mod 之值。
使用一個迴圈從 1 遍歷到 ,如果當前遍歷到的數字是 2 的冪,增加 len
的長度, len
為當前數字其二進位表示的長度,然後將其與上一個結果串接起來,最終得到新的結果。
在函式中使用 !(i & i - 1)
來判斷 i
是否為 2 的冪,如果一個數字 i
是 2 的冪, i - 1
會剛好等於 ~i
,而 i & ~i
會等於 0
,因此可以透過 !(i & i - 1)
來判斷 i
是否為 2 的冪。
在遍歷 1 到 的過程中,使用 i | (ans << len)
, ans
代表上一次計算所得的結果,使用了 long
型別的變數來存儲它,以避免整數溢出問題,len
為當前數字其二進位表示的長度,也是 ans
需向左位移的距離, ans
會向左位移 len
個 bits ,以便將 i
串接在後面。
最終,函式返回一個整數,即串接後的二進位表示對 取模的結果。
__builtin_clz
改寫__builtin_clz
函式會返回一個整數的二進制表示中,左起第一個 1 之前的 0 的個數,可以利用此功能來改寫程式中判斷一個整數是不是 2 的冪的部分。
首先我給定一個變數 int_bits
表示 int
型別的位元數,計算方式為 sizeof(int) * 8
, sizeof(int)
回回傳 int
型別的位元組 (byte) 數,每個位元組由 8 個位元組成,因此 sizeof(int) * 8
可得到 int
型別的位元數。
接著用 你所不知道的 C 語言: bitwise 操作 所提到的 clear a bit 的操作來將整數 i
二進制表示中最左側的 1
設為 0
,透過 ~(1 << int_bits - __builtin_clz(i) - 1)
可以找到要將其設為 0
的那個位元,然後把它與 i
做 and 運算,如此便可將整數 i
二進制表示中最左側的 1
變為 0
。
若 i
為 2 的冪,其二進制表示中應該只包含一個 1
,將其設為 0
會使得整數 i
變成 0
,因此若是在經過上述的操作後 i
變成 0
,就表示 i
是 2 的冪。
__builtin_ctz
改寫__builtin_ctz
函式會返回一個整數的二進制表示中,右起第一個 1 右側的 0 的個數,可以利用此功能來改寫程式中判斷一個整數是不是 2 的冪的部分。
使用 ~(1 << __builtin_ctz(i))
可以找到要將其設為 0
的那個位元,然後把它與 i
做 and 運算,如此便可將整數 i
二進制表示中最右側的 1
變為 0
。
若 i
為 2 的冪,其二進制表示中應該只包含一個 1
,將其設為 0
會使得整數 i
變成 0
,因此若是在經過上述的操作後 i
變成 0
,就表示 i
是 2 的冪。
__builtin_ffs
改寫__builtin_ffs
函式會返回一個整數的二進制表示中,右起第一個 1 的位置,假設 i = 00001000
,則 __builtin_ffs(i)
會返回 4
,可以利用此功能來改寫程式中判斷一個整數是不是 2 的冪的部分。
使用 ~(1 << __builtin_ffs(i) - 1)
可以找到要將其設為 0
的那個位元,然後把它與 i
做 and 運算,如此便可將整數 i
二進制表示中最右側的 1
變為 0
。
若 i
為 2 的冪,其二進制表示中應該只包含一個 1
,將其設為 0
會使得整數 i
變成 0
,因此若是在經過上述的操作後 i
變成 0
,就表示 i
是 2 的冪。
3
swar_count_utf8
運作原理swar_count_utf8
函式使用 SWAR 的技巧來計算一個給定的 UTF-8 字串中的 Unicode 字元數量。
首先,把 UTF-8 字串 buf
轉換成一個指向 uint64_t
整數類型的指標 qword
,這樣就可以按照 uint64_t
的位數(64 位)進行處理,便於後續計算。接著計算 qword
的終止條件 end
。
end
的計算方式為 qword + (len >> 3)
, len >> 3
相當於 len / 8
,( 因為 uint64_t
的大小是 8 個位元組 ),然後加上 qword
的初始值,得到 end
的位置,也就是將 buf
所占的總位元組每 8 個為一組分組後,剩餘的位元組開頭的位置 ( UTF-8 字串 buf
所占的總位元組數 len
不一定是 8 的倍數,因此可能會有剩餘 )。
然後,對於每個 64-bit 無號整數,使用以下步驟操作來計算其中後續位元組 ( continuation byte(s) ) 的數量:
以 t0 = [11xx.xxxx|10xx.xxxx|01xx.xxxx|00xx.xxxx|11xx.xxxx|10xx.xxxx|01xx.xxxx|00xx.xxxx]
舉例
not bit6 and bit7
以 __builtin_popcountll
計算,即 __builtin_popcountll(t4)
,並將結過加到 count
裡。
重複上述步驟,最後得到的 count
即為後續位元組 ( continuation byte(s) ) 的數量。
(1 << 3) * (len / 8)
將 len
除以 8,然後乘以 8(即左移 3 位),可使得位元組數量變成 8 的倍數 ( 為了配合 uint64_t
的位數(8 bytes)進行處理 ),接著減去後續位元組 ( continuation byte(s) ) 的數量,就可得到 UTF-8 字元的數量。
因為 UTF-8 字串 buf
所占的總位元組數 len
不一定是 8 的倍數,所以要計算 len % 8
來判斷 end
是否有指向 buf
尾端剩餘的位元組,當除數為 時,可以 len & n - 1
來表示對 做模運算,因此 len % 8
可改寫為 len & 7
,假設所得的餘數大於 0
,則使用 count_utf8
函式來處理剩餘的位元組,計算剩餘的位元組中所包含的 UTF-8 字元的數量,並將結果加到 count
,若餘數等於 0
則將 0
加到 count
,最後回傳 count
。
4
從上面的 pattern 可看出,函式 is_pattern
會篩選出 16 位無號整數 x
的二進制表示中,有連續的 1
集中在左側而連續的 0
集中在右側的數。
精簡版的程式碼使用了二補數的概念,對輸入的無號整數 x
取反加 1 得到 n
,然後將 n
與 x
做 XOR 運算,如果 x
符合上述的 pattern,n
會是 2 的冪,其值為 x
的二進制表示中,最右側那個 1
的位元所代表的數,所以 n ^ x
計算的結果相當於消除 x
的二進制表示中最右側那個 1
,因此,如果 (n ^ x) < x
,則表示 x
符合上述 pattern,這就是 is_pattern
函式要檢查的模式,因此返回 true,否則返回 false。