Assembly 計算位數(shù)的3種方法

2018-10-28 11:45 更新

前面介紹了一個簡單的技術(shù)來計算一個雙字中“on”的位數(shù)有多少。這一節(jié)來看看其它不是很直接來做這件事的方法,就當(dāng)作使用在這一章中討論的位操作的練習(xí)。

方法一

第一個方法很簡單,但不是很明顯。圖3.6展示了代碼。


方法一

這個方法為什么會起作用?在循環(huán)中的每一次重復(fù),data中就會有一個比特位被關(guān)閉了。當(dāng)所有的位都關(guān)閉了(也就是說, 當(dāng)data 等于0了),循環(huán)就結(jié)束了。使data等于0需要重復(fù)的次數(shù)就等于原始值data中比特位為1的位數(shù)。


第6行表示data中的一個比特位被關(guān)閉了。這個是如何起作用的?考慮data二進(jìn)制表示法最普遍的格式和在這種表示法中最右邊的1。根據(jù)上面的定義,在這個1后面的所有位都為0。現(xiàn)在,data -1的二進(jìn)制表示是什么樣的?最右邊的1的所有左邊的位與data是一樣的,但是在最右邊的1這一點(diǎn)的位將會是data原始位的反碼。例如:

data        = xxxxx10000
data - 1   = xxxxx01111


x表示對于在這個位上這兩個數(shù)的值是相等的。當(dāng)data和data - 1進(jìn)行AND運(yùn)算后,在data中的最右邊的1這一位的結(jié)果就會為0,而其它比特位沒有被改變。


方法二

查找表同樣可以用來計算出任意雙字的位數(shù)。這個直接方法首先要算出每個雙字的位數(shù),還要把位數(shù)儲存到一個數(shù)組中。但是,有兩個與這個方法相關(guān)的問題。雙字的值大約有40億。這就意味著數(shù)組將會非常大而且會浪費(fèi)很多時間在初始化這個數(shù)組上。(事實(shí)上,除非你確實(shí)打算使用一個超過40億的數(shù)組,否則花在初始化這個數(shù)組的時間將遠(yuǎn)遠(yuǎn)大于用第一種方法計算位數(shù)的時間。


一個更現(xiàn)實(shí)的方法是提前算出所有可能的字節(jié)的位數(shù),并把它們儲存到一個數(shù)組中。然后,雙字就可以分成四個字節(jié)來求。這四個字節(jié)的位數(shù)通過查找數(shù)組得到,然后將它們相加就得到原始雙字的位數(shù)。圖3.7展示了如何用代碼實(shí)現(xiàn)這個方法。


方法二

initialize_count bits函數(shù)必須在第一次調(diào)用count bits函數(shù)之前被調(diào)用。這個函數(shù)初始化了byte_bit_count全局?jǐn)?shù)組。count_bits函數(shù)并不是以一個雙字來看對待data變量,而是以把它看成四個字節(jié)的數(shù)組。byte指針作為一個指向這個四個字節(jié)數(shù)組的指針。因此,byte[0]是data中的一個字節(jié)(是最低有效字節(jié)還是最高有效字節(jié)取決于硬件是使用little還是big endian。)。當(dāng)然,你可以像這樣使用一條指令:

(data >> 24) & 0x000000FF


來得到最高有效字節(jié)值,可以用同樣的方法得到其它字節(jié);但是這些指令會比引用一個數(shù)組要慢。
最后一點(diǎn),使用for循環(huán)來計算在22和23行的總數(shù)是簡單的。但是,for循環(huán)就會包含初始化一個循環(huán)變量,在每一次重復(fù)后比較這個變量和增加這個變量的時間開支。通過清楚的四個值來計算總數(shù)會快一些。事實(shí)上,一個好的編譯器會將for循環(huán)形式轉(zhuǎn)換成清楚的求和。這個簡化和消除循環(huán)重復(fù)的處理是一個稱為循環(huán)展開的編譯器優(yōu)化技術(shù)。


方法三

現(xiàn)在還有一個更聰明的方法來計算在數(shù)據(jù)里的位數(shù)。這個方法逐位地相加數(shù)據(jù)的0位和1位。得到的總數(shù)就等于在這個數(shù)據(jù)中1的位數(shù)。例如,考慮計算儲存在data變量中的一個字節(jié)中為1的位數(shù)。第一步是執(zhí)行下面這個操作:

data = (data & 0x55) + ((data >> 1) & 0x55);

這個做了些什么?十六進(jìn)制常量0x55的二進(jìn)制表示為01010101。在這個加法的第一個操作數(shù)中,data與這個常量進(jìn)行了AND運(yùn)算,奇數(shù)的位就被拿出來了。第二操作數(shù)((data >> 1) & 0x55),首先移動所有的偶數(shù)位到奇
數(shù)位上,然后使用相同的掩碼得到這些相同的位?,F(xiàn)在,第一個操作數(shù)含有data的奇數(shù)位而第二個操作數(shù)含有偶數(shù)位。把這兩個操作數(shù)相加就相當(dāng)于把data的奇數(shù)位和偶數(shù)位相加。例如,如果data等于,那么:

實(shí)例1

顯示在右邊的加法展示了實(shí)際的位相加。這個字節(jié)的位被分成了四個2位的字段來展示實(shí)際上執(zhí)行了四個獨(dú)立的加法。因?yàn)樗羞@些字段的最大總數(shù)為2,總數(shù)超過它自身的字段且影響到其它字段的總數(shù)是不可能的。

當(dāng)然,總的位數(shù)還沒被計算出來。但是,可以使用跟上面一樣的技術(shù),經(jīng)過同樣的步驟來計算總數(shù)。下一步應(yīng)該是:

實(shí)例2

現(xiàn)在有兩個4位的字段被單獨(dú)地相加。

下一步是通過把這兩位的總數(shù)相加來得到最終的結(jié)果:

data = (data & 0x0F) + ((data >> 4) & 0x0F);

使用上面的例子(data等于):

實(shí)例3

現(xiàn)在data等于5,這正好是正確的結(jié)果。圖3.8實(shí)現(xiàn)了用這個方法來計算一個雙字的位數(shù)。它使用了一個for循環(huán)來計算總數(shù)。把循環(huán)展開可能會更快一點(diǎn),但是使用循環(huán)能清晰地看到這個方法產(chǎn)生的不同大小的數(shù)據(jù)。

方法三


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號