views:

53

answers:

5

Given a series of bits, what's the best way to overwrite a particular range of them.

For example, given:

0100 1010

Say I want to overwrite the middle 2 bits with 10 to make the result:

0101 0010

What would be the best way of doing this?

At first, I thought I would just shift the overwriting bits I want to the correct position (10000), and then use a bitwise OR. But I realized that while it preserves the other bits, there's no way of specifying which bits I want to actually overwrite.

I was looking into Python's bitarray module, but I just want to double-check that I'm not looking over an extremely simple bitwise operation to do this for me.

Thanks.

+1  A: 

This is done by first masking the bits you want to erase (forcing them to zero while preserving the other bits) before applying the bitwise OR.

Use a bitwise AND with the pattern (in this case) 11100111.

If you already have a "positive" version of the pattern (here this would be 00011000), which is easier to generate, you can obtain the "negative" version 11100111 using what is called 1's complement, available as ~ in Python and most languages with a C-like syntax.

Pascal Cuoq
Ah, I see. Sorry if this is another simple answer (I'm not very knowledgeable when it comes to bitwise stuff), but what would be the easiest way of generating the pattern 11100111 for example.For the wrong method I posted above, I would just shift the pattern I wanted to the desired position because all the trailing and leading 0's would be taken care of.For masking the bits I want to erase, I thought of shifting ones to the desired position and then inverting the bit string, but that doesn't take care of the leading 1's.Thanks again for any help.
noisesolo
@noisesolo I answered your comment by adding a paragraph to my answer above about the `~` operation in Python.
Pascal Cuoq
Thanks a lot. This is exactly what I needed.
noisesolo
+1  A: 

You can do it in 2 steps:

  1. Create a number with all the 0's you want to write in that looks like 1111 0111 and use an AND
  2. Create a number with all the 1's that looks like 0001 0000 and use an OR

BTW: The first number can easily be created by subtracting a 1 shifted into the position of your zeros from 255. So for example, do 255 - (0000 1000).

    a = 255 - (1 << x)
    b = 1 << y
    result = (source & a) | b

where x and y are the positions of your respective bits.

JGord
+1  A: 
>>> bin(0b01001010 & 0b11110111 | 0b00010000)
'0b1010010'
gnibbler
+3  A: 
a = 0b01001010
a &= 0b11100111
a |= 0b00010000

The and first zeroes the target bits:

  01001010
& 11100111
  --------
  01000010

then the or fills them with the desired data:

  01000010
| 00010000
  --------
  01010010
psicopoo
+2  A: 

I see you got excellent answers in the "bit manipulation" vein. If you have to do a lot of this, though, I would still recommend reading the discussion here and links therefrom, and, as that wiki suggests, a few of the packages found this way (BitVector, BitPacket, bitarray) -- readability is important, and experience shows that removing non-obvious inline code from your application-level flow in favor of calls to well-named APIs enhances it.

If you do decide to go with manipulation, automatic generation of the bit-ranges masks given bit-indices is clearly crucial. I would recommend starting with an "atomic bitmask" with just one 1 bit, built by shifting:

>>> bin(1 << 7)
'0b10000000'

as you see, 1 << 7 has a one followed by 7 zeros -- and therefore:

>>> bin((1 << 7) - 1)
'0b1111111'

(1 << 7) - 1 has exactly 7 ones (you need the parentheses because the priority of the shifting operator << is lower than that of the subtraction operator -) as the least significant bits aka LSB (and of course all zeros left of that).

Given an easy way to make "a bitmask with N ones" (as the LSB), making ones with bits N included to M excluded set and all other cleared is easy -- using named functions for extra readability:

>>> def n_lsb(x): return (1 << x) - 1
... 
>>> def n_to_m(n, m): return n_lsb(n) & ~ n_lsb(m)
... 
>>> bin(n_to_m(7, 3))
'0b1111000'

So, now, to set bits N included to M excluded to a certain pattern, as other answers have shown:

>>> def set_bits(i, n, m, pat): return (i & ~n_to_m(n, m))|(pat<<m)
... 
>>> bin(set_bits(511, 7, 3, 0b0101))
'0b110101111'

While this answer does not, per se, allow you to do anything more than the others, I do hope it can help "teach you to fish" (vs. just giving you a fish;-) and thereby help you (and other readers) in the future.

BTW, I would recommend reducing to a minimum the mix of bitwise and arithmetic operations -- in this A the only arithmetic operation I use is that - 1 in n_lsb, a hopefully very clear case; in more general cases, two's complement arithmetic (what ordinary integer arithmetic looks like when looked at from a bitwise point of view) could be tricky.

Alex Martelli