next_pow2
可針對給定無號 64 位元數值 x
,找出最接近且大於等於 2 的冪的值,例如:
next_pow2(7)
= 8next_pow2(13)
= 16next_pow2(42)
= 64以下是可能的實作方式:
然而上述程式碼存在分支,於是我們可考慮建構以下「填補」位元表示中的 1
:
最初 x
是 0010000000000000
,經過一系列操作後,成為 0011111111111111
,亦即設定 (set,即指派為 1
) 自原本最高位元到最低位元中,所有的位元。
我們嘗試改寫為以下:
請補完程式碼,使其符合預期。作答規範:
AAAA
和 BBBB
皆為數值,且 AAAA
小於 BBBB
CCCC
為表示式延伸問題:
__builtin_clzl
改寫
int __builtin_clz (unsigned int x)
Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined.
int __builtin_clzl (unsigned long)
Similar to__builtin_clz
, except the argument type is unsigned long.
clz
內建函式已運用時,編譯器能否產生對應的 x86 指令?
提示: 可執行
cc -O2 -std=c99 -S next_pow2.c
並觀察產生的next_pow2.s
檔案,確認bsrq
指令 (表示 "Bit Scan Reverse")
程式碼輸入範圍為~,將數值帶入前七個式子裡時x |= x >> 1;
,相當於右移7次,並在前面填入7個1,跳過x |= x >> AAAA;
,並執行x |= x >> 16;
時,以為例,可看出中間多了8個0,因為本函式的目的為填補位元的1,於是可以推得AAAA的值為8,到執行x |= x >> BBBB;
前,共填補了1+7+8+16 = 32個1,輸入值最大有64個位元,於是還須填補32個1才能將所有位元補滿,於是可推得BBBB為32,因為最大只會有64個1,所以超過的值,其最接近的2的冪無法表示出來。最後將x + 1
即為最接近且大於等於 2 的冪的值,但是當輸入為2的冪時,輸出會是大於輸入的值,例如輸入64,輸出為128。
文字訊息不要用圖片展現。
AAAA = 8
BBBB = 32
CCCC = x + 1
程式碼
int __builtin_clz (unsigned int x)
會回傳自 MSB 為 1 ,其左邊 0 的數量
64 - 57 = 7,可以藉由這個函式得知 MSB 為 1 的位置,只須往左位移 1 bit 就是最接近且大於等於 2 的冪的值,所以 7 可以看作是位移量,將 1 向左移 7 bit 就是 127 最接近且大於等於 2 的冪的值 (128) ,可以改寫成:
LeetCode 1680. Concatenation of Consecutive Binary Numbers 給定一整數 ,回傳將 1 到 的二進位表示法依序串接在一起所得到的二進位字串,其所代表的十進位數字 mod 之值。
測試資料 1:
輸入: n = 1
輸出: 1
"1" 於二進位中對應著十進位中
1
的值
測試資料 2:
輸入: n = 3
輸出: 27
二進位中,1 、 2 、 3 對應著
1
,10
和11
。
將它們串接在一起,我們得到11011
,其對應著十進位數值27
測試資料 3:
輸入: n = 12
輸出: 505379714
串接結果為
1101110010111011110001001101010111100
該十進位值為118505380540
mod 後,結果為505379714
以下是可能的實作:
請補完程式碼,使其符合預期。作答規範:
DDDD
和 EEEE
皆為表示式延伸問題:
__builtin_{clz,ctz,ffs}
改寫,並改進 mod 的運算DDDD
是為了判斷 i 是否為 2 的冪,將 2 的冪轉成二進位,可以發現除了最高位元是 1 其餘都是 0,又可以發現,2 的冪減 1 後,剛好等於原數 NOT 後的值。在數位邏輯中 N & !N = 0
,我們可以透過這個方法來判斷 N 是否為 2 的冪,所以是否為 2 的冪的判斷規則為N & (N-1) == 0
,也就是!(i & (i - 1))
。
ans每次須左移,才能將後續的二進位數值放入,位移量就是 len 的值,len 的值要增加,需要上述判斷式為 2 的冪才會增加,當 i = 1 時,len = 1,當 i = 2 時,len = 2,所以 len 恰為所需的位移量,於是為ans << len
。
DDDD = i & (i - 1)
EEEE = ans << len
程式碼
int __builtin_clz (unsigned int x)
會回傳自 MSB 為 1 ,其左邊 0 的數量int __builtin_ctz (unsigned int x)
會回傳自 LSB 為 1 ,其右邊 0 的數量int __builtin_ffs (unsigned int x)
會回傳 1 + LSB 為 1 的 index可以觀察到,當輸入為 2 的冪時,其__builtin_clz(x) + __builtin_ffs(x)
會等於 bits 數,所以判斷式可以改寫成:
SWAR 通常不易實作,因此,我們觀察它們的位元模式:(從最低位元起算) 第 7 位元為 1,且第 6 位元為 0。於是我們可採用以下表示式:
再計算 (使用 population count 指令) 有多少位元組裡頭的 1 (即具有此屬性)。__builtin_popcount
SWAR 實作如下:
為了提升效能,將1 byte 的 char *
強制轉形成8 bytes 的 qword
,因為每次讀取8個bytes,所以字串總長度要除8,也就是 len >> 3
。
迴圈會去計算 continuation bytes 數量。
是為了計算出能被8 bytes整除中的 UTF-8 字元數量,(1 << 3) * (len / 8)
能計算出總共有多少能被8 bytes 整除的 bytes 數量,再去減掉 continuation bytes 數量,即為 UTF-8 字元數量。
這部份是用來計算那些無法被8 bytes 整除的部份,使用 count_utf8
來計算剩餘的 UTF-8 字元數量,再加上之前計算的 UTF-8 字元數量,即為總字元數量。
AAAA = 3
BBBB = 7
CCCC = 7
DDDD = 0
程式碼
以下程式碼可判定 16 位元無號整數是否符合特定樣式 (pattern):
符合上述程式碼的樣式,如下:
改寫上述程式碼,使其達到等價行為,但更精簡有效。
若為符合 pattern 的數值,在做完二補數後,其值會是最左邊的一個 bit 為 1,其餘是 0 ,再對自己做 XOR ,則會使原本值的最右邊的 1 變成 0 ,故結果會小於原本的值。若為不符合 pattern 的數值,再做完 XOR ,其值會是除了最右邊一個 bit 為 0 ,其餘為 1 ,故結果會大於原本的值。
EEEE = -x
FFFF = x
程式碼