One function with different arguments to push certain bits of an input integer to the left

旧时模样 提交于 2020-01-23 03:59:08

问题


This question is related to this.

Let x be an 8 bit integer. I will number bits from left to right because that's how we read. If I want to get the third and fifth bits and put them in the first and second bits, with everything else as zero, I can have f(x) = (5*x) & 0b11000000. More concisely:

00a0b000 -> ab000000 | f_0b00101000(x) = (5*x) & 0b11000000

However if I want the fifth, sixth and eighth bits to be in the first three bits, f(x) is different:

000ab0cd -> abcd0000 | f_0b00011011(x) = ((x << 3) & 0b11000000) | (x << 4)

Note that the n in f_n(x) indicates which bits I care about. n could have any 8 bit value. Is there some common function of x (and a few arguments) that will push all the required bits to the left? Also, this is supposed to be fast so I would prefer to only use bitwise operations.


Let's say instead I'm dealing with 3 bit integers.

000 -> 000 | f_0(x) = (1*x) & 0
00a -> a00 | f_1(x) = (4*x) & 4
0a0 -> a00 | f_2(x) = (2*x) & 4
0ab -> ab0 | f_3(x) = (2*x) & 6
a00 -> a00 | f_4(x) = (1*x) & 4
a0b -> ab0 | f_5(x) = (3*x) & 6
ab0 -> ab0 | f_6(x) = (1*x) & 6
abc -> abc | f_7(x) = (1*x) & 7

These functions can all be combined into f(x, m, a) = (m*x) & a, and I could create a lookup table which I could index to get the values of m and a from n:

t = [(1, 0), (4, 4), (2, 4), (2, 6), (1, 4), (3, 6), (1, 6), (1, 7)]

So f_n(x) becomes f(x, *t[n]). I think f(x, m, a) = (m*x) & a is optimal for 3 bits. The naive version for 3 bits is:

f(x, a, b, c, d, e, f) = ((x & a) << b) |
                         ((x & c) << d) |
                         ((x & e) << f)

The operation count is 8 for the naive function, and 2 for the optimal (?) one. Now the naive function for 8 bits is:

f(x, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) = ((x & a) << b) |
                                                       ((x & c) << d) |
                                                       ((x & e) << f) |
                                                       ((x & g) << h) |
                                                       ((x & i) << j) |
                                                       ((x & k) << l) |
                                                       ((x & m) << n) |
                                                       ((x & o) << p)

There are 25 operations in this instruction. Is it possible to get that down to around 7 or 8, or even lower?


回答1:


You could store functions in your lookup table:

table = [
    lambda x:x,
    lambda x:x,
    lambda x:x | ((x & 0b0001) << 1),
    ...
    ]

I would question why you are doing this by bit-shifting integers in Python. If it's for performance, I think doing this via a series of bit-shift operations is a bad idea. The interpreter overhead is going to utterly swamp any speed benefit that the bit-shifting could achieve. If it's for memory usage, because you need to fit millions of these in memory, you would probably do well to look at numpy, which can create memory-efficient arrays and apply operations simultaneously across the whole array.

One good reason I could think of for doing this in Python is because you want to experiment with the algorithm before implementing it in a low level language like C. Even then, I suspect the performance benefit would only be noticeable if you're working on very constrained hardware or doing some very intensive calculations - perhaps you are simulating tens of thousands of games of Bejeweled. If you are just doing this to calculate the logic for a single instance of the game played by a human in real-time, I doubt the bit-shifting optimization is going to be meaningful.

If you're intent on doing this with bit-shifting, could you perhaps do something like this? Rather than do it in a single step, loop until there are no more bits that can "fall". You should only need to loop at most as many times as you have bits in your integer.

Suppose you have this pattern:

        PATTERN : ab--c--d
          HOLES : 00110110

Find the highest set bit: the leftmost hole. (You can do something quite similar to this: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 )

  LEFTMOST HOLE : 00100000

Now eliminate just that hole. Everything on the left won't change. Everything on the right will shift left by one. Create masks for these areas.

       CAN FALL : 00011111  ( == LEFTMOST HOLE - 1)
   WON'T CHANGE : 11000000  ( == invert CAN FALL and shift left )

Use those masks to chop up, shift and reassemble all your other bitfields.

      KEEP THIS : ab
     SHIFT THIS :    -c--d
         RESULT : ab-c--d-

Finally, repeat the process until the CAN FALL mask selects only holes. It won't perform your operation in the strict minimum number of operations, but it should be relatively understandable and not require the complexity of large lookup tables.




回答2:


def f(t, x):  # template, number to convert
    t = bin(t).replace("0b", "").rjust(8, "0")
    x = bin(x).replace("0b", "").rjust(8, "0")
    return int("".join([x[i] for i, b in enumerate(t) if int(b)]).ljust(8, "0"), 2)


来源:https://stackoverflow.com/questions/25564262/one-function-with-different-arguments-to-push-certain-bits-of-an-input-integer-t

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!