1

I already have a function able to extract specific bits from a value:

def get_bits(n, start, end, length=64):
    """Read bits [<start>:<end>] of <n> and return them
    <length> is the bitlength of <n>"""
    shift = length - end
    mask = 1 << (end - start) - 1
    return (n & (mask << shift)) >> shift

I need a similar function to change said bits :

def set_bits(n, start, end, newValue, length=64):
    """Set bits [<start>:<end>] of <n> to <newValue> and return it
    <length> is the bitlength of <n>"""
    pass #How do I do this ?

I've tried figuring it out on paper and looking it up, but I'm afraid my abilities in bitwise maths are pretty poor and I can't find a solution that fits


Example of wanted behavior :

n = 341      #341 is 101010101
newValue = 6 #6 is 0110
n = set_bits(
    n = n, 
    start = 2, 
    end = 6, 
    newValue = newValue, 
    length = 9)
# n should now be 309 (100110101)
ice-wind
  • 690
  • 4
  • 20
  • 1
    Maybe this could help: https://stackoverflow.com/questions/54369088/c-macro-to-set-multiple-bits, https://stackoverflow.com/questions/6556961/use-of-the-bitwise-operators-to-pack-multiple-values-in-one-int – ForceBru Feb 04 '21 at 10:21
  • @ForceBru I don't understand C syntax, so I don't understand the answers :/ I have a really hard time telling apart what's part of the solution and what's syntax, what's a variable and what's a keyword etc – ice-wind Feb 04 '21 at 10:26
  • Changing a variable is not something you can do, only values. Do you want to *get a new value with changed bits* or *change the bits on an existing value*? The code suggests the former, the docstring suggests the latter. – MisterMiyagi Feb 04 '21 at 10:37
  • @MisterMiyagi I thought it was pretty obvious I didn't want to do the thing that's literally impossible. Of course I want a new value, but since reassignment and modification behave almost exactly the same it doesn't really matter – ice-wind Feb 04 '21 at 10:51
  • @AmyLucyRose No offence meant. A lot of people ask for the impossible, so wanted to make sure. Thanks for clarifying. – MisterMiyagi Feb 04 '21 at 10:54

1 Answers1

1

You can do something like this:

def set_bits(n, start, end, new_value, length=64):
    # Remove all the bits in the range from the original number
    # Do this by using `AND` with the inverse of the bits
    n = n & ~((2 ** (end-start) - 1) << (length - end))
    # OR with the new value
    n = n | new_value << (length - end)
    return n

Demonstration:

>>> set_bits(341, 2, 6, 6, 9)
309
mousetail
  • 7,009
  • 4
  • 25
  • 45
  • I don't understand how this works, since ```~((2 ** (end-start) - 1) << (length - end))``` should only be 7 bits long, wouldn't python implicitly ```&``` the first two bits of ```n``` with 0s and return the wrong value (```53``` with the example numbers) ? I can verify it works correctly in practice but please help me understand it before I implement it ! – ice-wind Feb 04 '21 at 10:47
  • The `~` operator will flip all 32 or 64 bits (depending on your operating system), not just the last 8. – mousetail Feb 04 '21 at 10:57
  • Oh that makes sense ! I guess the fact that ```bin()``` refuses to show the REAL bit value is throwing me off, the mask shows as ```'-0b1111001'``` which is extremely unhelpful – ice-wind Feb 04 '21 at 11:00
  • That happens because numbers are stored in [2s complement](https://en.wikipedia.org/wiki/Two%27s_complement) so numbers with the first bit set show up as negative – mousetail Feb 04 '21 at 11:02
  • Well I understand that now but honestly it would be a lot more helpful if bin(-121) returned ```'0b1000000000000000000000000000000000000000000000000000000001111000'``` – ice-wind Feb 04 '21 at 11:04
  • A workaround is calling `bin(number + sys.maxsize + 1)` – mousetail Feb 04 '21 at 11:06
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/228254/discussion-between-amylucyrose-and-mousetail). – ice-wind Feb 04 '21 at 11:08
  • @AmyLucyRose The issue is that Python integers are arbitrary precision, so the "leading" bit is not fixed. ``0b1000000000000000000000000000000000000000000000000000000001111001`` is actually ``9223372036854775929``. Might be relevant: [Why the binary representation is different from python compiler than what we know on paper?](https://stackoverflow.com/questions/62332193/why-the-binary-representation-is-different-from-python-compiler-than-what-we-kno) – MisterMiyagi Feb 04 '21 at 11:12