Count the consecutive zero bits (trailing) on the right with multiply and lookup

我在网上看到了一点神奇的代码,用来计算一个数字末尾连续零的个数。

刚好我在优化一个I2C读写函数(只写入I2C特定bit),觉得这个很有用。经过尝试,确实没问题。

下面我隆重介绍一下:

Count the consecutive zero bits (trailing) on the right with multiply and lookup

unsigned int v;  // find the number of trailing zeros in 32-bit v 
int r;           // result goes here
static const int MultiplyDeBruijnBitPosition[32] = 
{
  0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 
  31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
r = MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x077CB531U)) >> 27];

Converting bit vectors to indices of set bits is an example use for this. It requires one more operation than the earlier one involving modulus division, but the multiply may be faster. The expression (v & -v) extracts the least significant 1 bit from v. The constant 0x077CB531UL is a de Bruijn sequence, which produces a unique pattern of bits into the high 5 bits for each possible bit position that it is multiplied against. When there are no bits set, it returns 0. More information can be found by reading the paper Using de Bruijn Sequences to Index 1 in a Computer Word by Charles E. Leiserson, Harald Prokof, and Keith H. Randall.

On October 8, 2005 Andrew Shapira suggested I add this. Dustin Spicuzza asked me on April 14, 2009 to cast the result of the multiply to a 32-bit type so it would work when compiled with 64-bit ints.

以上内容转自http://graphics.stanford.edu/~seander/bithacks.html

 ++++++++++++++++++++++++++++++++我是分割线++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

然后根据此方案,我们来做一个8bit的末尾连续0的计算。

主要用途是I2C读写时,mask 的计算。如果要读写某Byte的某几个bit,可以只传入寄存器地址和Mask位就可以了。

创建文件test.c

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 void main(int argc, **argv)
 5 {
 6     int r;
 7     unsigned char v;  
 8     static const unsigned char MultiplyDeBruijnBitPosition[8] = {0, 1, 6, 2, 7, 5, 4, 3};
 9 
10     v = (unsigned char)stroul(argv[1],NULL, 0);
11     r = MultiplyDeBruijnBitPosition[((unsigned char)((v & -v) * 0x1DU)) >> 5];
12 
13     printf("The calculated = %d
", r);
14 }

在Linux下调试验证:

gcc -o test test.c

./test xxx

应用实例:

mask的计算方法

1 #define BIT(nr) (1<<(nr))
2 #define _PM_MASK(BITS, POS) 
3     ((unsigned char)(((1 << (BITS)) - 1) << (POS)))
4 #define PM_MASK(LEFT, RIGHT) 
5     _PM_MASK((LEFT) - (RIGHT) + 1, RIGHT)

下面是带mask的I2C写操作。

static status_t i2c_write_mask(uint8_t reg, uint8_t mask, uint8_t data)
{
    status_t status;
    uint8_t shift = 0;
    uint8_t tmp;

    status = i2c_read_byte(reg, &tmp);
    if (status != kStatus_Success) {
        PRINTF("Failed: status=%ld, reg=%d
", status, reg);
        goto out;
    }

    tmp &= ~mask;
    /* 0x1D is calculated from the de Bruun sequence */
    shift = MultiplyDeBruijnBitPosition[((uint8_t)((mask & -mask) * 0x1DU)) >> 5];
    data <<= shift;
    tmp |= data & mask;

    status = i2c_write_byte(reg, tmp);
    if (status != kStatus_Success) {
        PRINTF("Failed: reg=%02X, status=%ld
", reg, status);
    }

out:
    return status;
}

至于像 0x1DU 及类似的代码中的 0x077CB531,0x5F3759DF和数组

{
  0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
  31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};

{0, 1, 6, 2, 7, 5, 4, 3};

的由来问题,就参考:http://www.cnblogs.com/shangdawei/p/3967505.html 好了,非常感谢此作者的工作,让我很快明白了De Bruijin序列的计算方法。

原文地址:https://www.cnblogs.com/Hello-words/p/7615746.html