codecamp

Assembly 计算位数的3种方法

前面介绍了一个简单的技术来计算一个双字中“on”的位数有多少。这一节来看看其它不是很直接来做这件事的方法,就当作使用在这一章中讨论的位操作的练习。

方法一

第一个方法很简单,但不是很明显。图3.6展示了代码。


方法一

这个方法为什么会起作用?在循环中的每一次重复,data中就会有一个比特位被关闭了。当所有的位都关闭了(也就是说, 当data 等于0了),循环就结束了。使data等于0需要重复的次数就等于原始值data中比特位为1的位数。


第6行表示data中的一个比特位被关闭了。这个是如何起作用的?考虑data二进制表示法最普遍的格式和在这种表示法中最右边的1。根据上面的定义,在这个1后面的所有位都为0。现在,data -1的二进制表示是什么样的?最右边的1的所有左边的位与data是一样的,但是在最右边的1这一点的位将会是data原始位的反码。例如:

data        = xxxxx10000
data - 1   = xxxxx01111


x表示对于在这个位上这两个数的值是相等的。当data和data - 1进行AND运算后,在data中的最右边的1这一位的结果就会为0,而其它比特位没有被改变。


方法二

查找表同样可以用来计算出任意双字的位数。这个直接方法首先要算出每个双字的位数,还要把位数储存到一个数组中。但是,有两个与这个方法相关的问题。双字的值大约有40亿。这就意味着数组将会非常大而且会浪费很多时间在初始化这个数组上。(事实上,除非你确实打算使用一个超过40亿的数组,否则花在初始化这个数组的时间将远远大于用第一种方法计算位数的时间。


一个更现实的方法是提前算出所有可能的字节的位数,并把它们储存到一个数组中。然后,双字就可以分成四个字节来求。这四个字节的位数通过查找数组得到,然后将它们相加就得到原始双字的位数。图3.7展示了如何用代码实现这个方法。


方法二

initialize_count bits函数必须在第一次调用count bits函数之前被调用。这个函数初始化了byte_bit_count全局数组。count_bits函数并不是以一个双字来看对待data变量,而是以把它看成四个字节的数组。byte指针作为一个指向这个四个字节数组的指针。因此,byte[0]是data中的一个字节(是最低有效字节还是最高有效字节取决于硬件是使用little还是big endian。)。当然,你可以像这样使用一条指令:

(data >> 24) & 0x000000FF


来得到最高有效字节值,可以用同样的方法得到其它字节;但是这些指令会比引用一个数组要慢。
最后一点,使用for循环来计算在22和23行的总数是简单的。但是,for循环就会包含初始化一个循环变量,在每一次重复后比较这个变量和增加这个变量的时间开支。通过清楚的四个值来计算总数会快一些。事实上,一个好的编译器会将for循环形式转换成清楚的求和。这个简化和消除循环重复的处理是一个称为循环展开的编译器优化技术。


方法三

现在还有一个更聪明的方法来计算在数据里的位数。这个方法逐位地相加数据的0位和1位。得到的总数就等于在这个数据中1的位数。例如,考虑计算储存在data变量中的一个字节中为1的位数。第一步是执行下面这个操作:

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

这个做了些什么?十六进制常量0x55的二进制表示为01010101。在这个加法的第一个操作数中,data与这个常量进行了AND运算,奇数的位就被拿出来了。第二操作数((data >> 1) & 0x55),首先移动所有的偶数位到奇
数位上,然后使用相同的掩码得到这些相同的位。现在,第一个操作数含有data的奇数位而第二个操作数含有偶数位。把这两个操作数相加就相当于把data的奇数位和偶数位相加。例如,如果data等于,那么:

实例1

显示在右边的加法展示了实际的位相加。这个字节的位被分成了四个2位的字段来展示实际上执行了四个独立的加法。因为所有这些字段的最大总数为2,总数超过它自身的字段且影响到其它字段的总数是不可能的。

当然,总的位数还没被计算出来。但是,可以使用跟上面一样的技术,经过同样的步骤来计算总数。下一步应该是:

实例2

现在有两个4位的字段被单独地相加。

下一步是通过把这两位的总数相加来得到最终的结果:

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

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

实例3

现在data等于5,这正好是正确的结果。图3.8实现了用这个方法来计算一个双字的位数。它使用了一个for循环来计算总数。把循环展开可能会更快一点,但是使用循环能清晰地看到这个方法产生的不同大小的数据。

方法三


Assembly Big和Little Endian表示法
Assembly 间接寻址及堆栈
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }